OOD - Parking Lot
Problem Statement
Design a comprehensive multi-level parking lot management system that efficiently handles vehicle parking operations across multiple floors. The system must support various vehicle types (motorcycles, cars, trucks, electric vehicles) with corresponding spot types (compact, standard, large, motorcycle, electric with charging).
Key requirements include:
- Real-time tracking of available parking spots across all floors
- Automated ticket generation at entry with timestamp
- Dynamic pricing calculation based on duration, vehicle type, and time of day (peak/off-peak)
- Multiple payment methods (cash, credit card, mobile payment) at exit
- Reservation system for advance spot booking
- Display boards on each floor showing real-time availability by spot type
- Administrative functions for adding/removing floors, spots, and configuring pricing
- Support for electric vehicle charging stations with separate billing
- Handling edge cases: full lot notification, lost ticket scenarios, spot maintenance mode
- Audit logging for all transactions and system events
Requirements Analysis
Functional Requirements
Core Operations:
-
Entry Management
- Issue parking tickets with unique IDs at entry gates
- Automatically detect vehicle type or allow manual selection
- Find and assign appropriate available spot based on vehicle type
- Refuse entry when no suitable spots available
- Support reservation validation at entry
-
Exit Management
- Scan ticket and calculate parking fee based on duration
- Process payment through multiple methods
- Release barrier after successful payment
- Handle lost ticket scenarios with maximum fee
-
Spot Management
- Track real-time occupancy status of all spots
- Support different spot types with size compatibility
- Mark spots for maintenance (temporarily unavailable)
- Optimize spot assignment (nearest available)
-
Pricing
- Hourly rates with different pricing tiers by vehicle type
- Peak and off-peak hour pricing
- Daily maximum cap
- Special rates for monthly pass holders
- Separate charging fees for electric vehicles
-
Reservation System
- Allow advance booking for specific time slots
- Guarantee spot availability for reservations
- Cancellation handling with refund policies
- Reservation expiration and spot release
-
Administrative Operations
- Add/remove/modify parking floors
- Configure spot types and counts per floor
- Update pricing strategies
- Generate reports (revenue, occupancy trends, peak hours)
- Monitor system health
Non-Functional Requirements
- Reliability: Zero ticket loss, accurate fee calculation, consistent state across concurrent operations
- Scalability: Support 1000+ spots across 10+ floors with 100s of concurrent entries/exits
- Performance: Ticket generation < 2 seconds, fee calculation < 1 second, real-time display updates
- Availability: 99.9% uptime, graceful degradation if payment gateway fails
- Security: Secure payment processing, tamper-proof tickets, audit trails
- Usability: Clear display boards, simple payment interface, multilingual support
Core Use Cases
graph TB subgraph Actors Customer[๐ค Customer] Attendant[๐ Parking Attendant] Admin[๐จโ๐ผ Administrator] System[๐ฅ๏ธ System] end subgraph "Core Use Cases" UC1[๐ซ Get Parking Ticket] UC2[๐ณ Pay and Exit] UC3[๐ Make Reservation] UC4[๐ Check Availability] UC5[๐ข Manage Floors] UC6[๐ ฟ๏ธ Manage Spots] UC7[๐ฐ Configure Pricing] UC8[๐ Generate Reports] UC9[๐บ Update Display Boards] UC10[๐ Charge Electric Vehicle] UC11[๐๏ธ Handle Lost Ticket] UC12[๐ง Mark Spot Maintenance] end Customer -->|enters| UC1 Customer -->|exits| UC2 Customer -->|books| UC3 Customer -->|views| UC4 Customer -->|uses| UC10 Attendant -->|assists| UC2 Attendant -->|processes| UC11 Admin -->|creates| UC5 Admin -->|configures| UC6 Admin -->|sets| UC7 Admin -->|views| UC8 Admin -->|schedules| UC12 System -->|automatically| UC9 style UC1 fill:#e1f5ff,stroke:#01579b,stroke-width:2px style UC2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style UC3 fill:#fff9c4,stroke:#f57f17,stroke-width:2px style UC4 fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px style UC5 fill:#ffe0b2,stroke:#e65100,stroke-width:2px style UC6 fill:#b2dfdb,stroke:#00695c,stroke-width:2px style UC7 fill:#ffccbc,stroke:#bf360c,stroke-width:2px style UC8 fill:#d1c4e9,stroke:#4527a0,stroke-width:2px style UC9 fill:#b2ebf2,stroke:#006064,stroke-width:2px style UC10 fill:#c5e1a5,stroke:#558b2f,stroke-width:2px style UC11 fill:#ffcdd2,stroke:#c62828,stroke-width:2px style UC12 fill:#ffe082,stroke:#f9a825,stroke-width:2px
Class Diagram
The design demonstrates several key patterns:
- Strategy Pattern: PricingStrategy for flexible pricing algorithms
- Observer Pattern: DisplayBoard observers get notified on spot availability changes
- Factory Pattern: ParkingSpotFactory creates appropriate spot types
- Singleton Pattern: ParkingLot ensures single instance coordination
- State Pattern: ParkingSpot states (Available, Occupied, Reserved, Maintenance)
classDiagram class ParkingLot { -String name -List~ParkingFloor~ floors -List~EntranceGate~ entrances -List~ExitGate~ exits -PricingStrategy pricingStrategy -Map~String,Ticket~ activeTickets -ReservationManager reservationManager +getInstance() ParkingLot +addFloor(floor) +getAvailableSpot(vehicleType) ParkingSpot +issueTicket(vehicle) Ticket +processExit(ticket, payment) Receipt +getTotalCapacity() int +getAvailableCount(spotType) int } class ParkingFloor { -int floorNumber -Map~SpotType,List~ParkingSpot~~ spots -DisplayBoard displayBoard +addSpot(spot) +removeSpot(spotId) +findAvailableSpot(vehicleType) ParkingSpot +getAvailableCount(spotType) int +notifyDisplayBoard() } class ParkingSpot { <<abstract>> -String spotId -SpotType spotType -SpotState state -Vehicle vehicle +isAvailable() boolean +assignVehicle(vehicle) +releaseVehicle() +setState(state) +getType() SpotType } class CompactSpot { +canFitVehicle(vehicle) boolean } class StandardSpot { +canFitVehicle(vehicle) boolean } class LargeSpot { +canFitVehicle(vehicle) boolean } class MotorcycleSpot { +canFitVehicle(vehicle) boolean } class ElectricSpot { -ChargingStation chargingStation +canFitVehicle(vehicle) boolean +startCharging() +stopCharging() +getChargingFee() Money } class SpotState { <<enumeration>> AVAILABLE OCCUPIED RESERVED MAINTENANCE OUT_OF_SERVICE } class Vehicle { <<abstract>> -String licensePlate -VehicleType type +getType() VehicleType } class Motorcycle { } class Car { } class Truck { } class ElectricCar { -int batteryCapacity +needsCharging() boolean } class VehicleType { <<enumeration>> MOTORCYCLE CAR TRUCK ELECTRIC_CAR VAN } class SpotType { <<enumeration>> MOTORCYCLE COMPACT STANDARD LARGE ELECTRIC } class Ticket { -String ticketId -DateTime issueTime -ParkingSpot assignedSpot -Vehicle vehicle -TicketStatus status +calculateFee() Money +getParkedDuration() Duration } class TicketStatus { <<enumeration>> ACTIVE PAID LOST RESERVED } class PricingStrategy { <<interface>> +calculateFee(ticket) Money } class HourlyPricing { -Map~VehicleType,Money~ hourlyRates -Money maxDailyRate +calculateFee(ticket) Money } class PeakHourPricing { -PricingStrategy baseStrategy -double peakMultiplier -TimeRange peakHours +calculateFee(ticket) Money +isPeakHour(time) boolean } class MonthlyPassPricing { -Map~String,Pass~ activePasses +calculateFee(ticket) Money +validatePass(passId) boolean } class Payment { -String paymentId -Money amount -PaymentMethod method -PaymentStatus status -DateTime timestamp +process() boolean } class PaymentMethod { <<enumeration>> CASH CREDIT_CARD DEBIT_CARD MOBILE_PAYMENT } class DisplayBoard { -int floorNumber -Map~SpotType,Integer~ availableCounts +update(spotType, availableCount) +displayAvailability() } class EntranceGate { -String gateId +issueTicket(vehicle) Ticket +validateReservation(reservationId) boolean } class ExitGate { -String gateId -PaymentProcessor paymentProcessor +scanTicket(ticketId) Ticket +processPayment(ticket, payment) Receipt +openBarrier() } class ReservationManager { -Map~String,Reservation~ reservations +createReservation(vehicle, duration) Reservation +cancelReservation(reservationId) +validateReservation(reservationId) boolean +findReservedSpot(reservationId) ParkingSpot } class Reservation { -String reservationId -Vehicle vehicle -DateTime startTime -DateTime endTime -ParkingSpot reservedSpot -ReservationStatus status +isValid() boolean +cancel() } class Admin { -String adminId -String name +addFloor(floor) +addSpot(floor, spot) +updatePricing(strategy) +generateReport(type) Report +markSpotMaintenance(spotId) } class ParkingAttendant { -String attendantId -String name +assistPayment(ticket, payment) Receipt +handleLostTicket(vehicle) Ticket +overrideGate() } ParkingLot "1" *-- "many" ParkingFloor ParkingLot "1" *-- "many" EntranceGate ParkingLot "1" *-- "many" ExitGate ParkingLot "1" --> "1" PricingStrategy ParkingLot "1" --> "1" ReservationManager ParkingFloor "1" *-- "many" ParkingSpot ParkingFloor "1" --> "1" DisplayBoard ParkingSpot <|-- CompactSpot ParkingSpot <|-- StandardSpot ParkingSpot <|-- LargeSpot ParkingSpot <|-- MotorcycleSpot ParkingSpot <|-- ElectricSpot ParkingSpot --> SpotState ParkingSpot --> Vehicle Vehicle <|-- Motorcycle Vehicle <|-- Car Vehicle <|-- Truck Vehicle <|-- ElectricCar Vehicle --> VehicleType Ticket --> ParkingSpot Ticket --> Vehicle Ticket --> TicketStatus PricingStrategy <|.. HourlyPricing PricingStrategy <|.. PeakHourPricing PricingStrategy <|.. MonthlyPassPricing Payment --> PaymentMethod ReservationManager "1" *-- "many" Reservation Reservation --> ParkingSpot Reservation --> Vehicle EntranceGate --> Ticket ExitGate --> Payment
Activity Diagrams
1. Customer Entry and Parking Flow
flowchart TD Start([๐ Vehicle Arrives at Entry]) --> CheckReservation{Has\nReservation?} CheckReservation -->|Yes| ValidateRes[๐ Validate Reservation ID] CheckReservation -->|No| DetectVehicle[๐ Detect/Scan Vehicle Type] ValidateRes --> ResValid{Valid?} ResValid -->|Yes| FindResSpot[๐ฏ Find Reserved Spot] ResValid -->|No| DisplayResError[โ Show Invalid Reservation] DisplayResError --> End1([๐ซ Entry Denied]) DetectVehicle --> FindSpot[๐ Search Available Spot<br/>for Vehicle Type] FindSpot --> SpotAvailable{Spot<br/>Available?} SpotAvailable -->|No| DisplayFull[โ Display Lot Full Message] DisplayFull --> End1 SpotAvailable -->|Yes| AssignSpot[โ Assign Spot to Vehicle] FindResSpot --> AssignSpot AssignSpot --> UpdateState[๐ Update Spot State to OCCUPIED] UpdateState --> GenerateTicket[๐ซ Generate Parking Ticket<br/>with Timestamp & Spot Info] GenerateTicket --> NotifyDisplay[๐บ Notify Display Board<br/>Decrement Available Count] NotifyDisplay --> PrintTicket[๐จ๏ธ Print/Issue Ticket] PrintTicket --> OpenBarrier[๐ง Open Entry Barrier] OpenBarrier --> VehicleEnters[๐ Vehicle Enters & Parks] VehicleEnters --> End2([โ Entry Complete]) style Start fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style CheckReservation fill:#fff9c4,stroke:#f57f17,stroke-width:2px style ValidateRes fill:#e1bee7,stroke:#6a1b9a,stroke-width:2px style DetectVehicle fill:#b2dfdb,stroke:#00695c,stroke-width:2px style FindSpot fill:#b3e5fc,stroke:#0277bd,stroke-width:2px style SpotAvailable fill:#fff59d,stroke:#f9a825,stroke-width:2px style DisplayFull fill:#ffcdd2,stroke:#c62828,stroke-width:2px style AssignSpot fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style UpdateState fill:#bbdefb,stroke:#1565c0,stroke-width:2px style GenerateTicket fill:#dcedc8,stroke:#558b2f,stroke-width:2px style NotifyDisplay fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style PrintTicket fill:#c5cae9,stroke:#3949ab,stroke-width:2px style OpenBarrier fill:#b2dfdb,stroke:#00796b,stroke-width:2px style VehicleEnters fill:#a5d6a7,stroke:#388e3c,stroke-width:2px style End1 fill:#ef5350,stroke:#c62828,stroke-width:3px style End2 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px style DisplayResError fill:#ffccbc,stroke:#d84315,stroke-width:2px
2. Exit and Payment Processing Flow
flowchart TD Start([๐ Vehicle Arrives at Exit]) --> ScanTicket[๐ Scan Parking Ticket] ScanTicket --> TicketValid{Ticket<br/>Found?} TicketValid -->|No| LostTicket[โ Lost Ticket Scenario] LostTicket --> AttendantHelp[๐ Call Attendant] AttendantHelp --> VerifyVehicle[๐ Verify Vehicle License Plate] VerifyVehicle --> MaxFee[๐ฐ Apply Maximum Daily Fee] TicketValid -->|Yes| CalcDuration[โฑ๏ธ Calculate Parked Duration] CalcDuration --> CheckPeak[๐ Check Peak Hour Status] CheckPeak --> ApplyPricing[๐ต Apply Pricing Strategy<br/>Hourly/Peak/Monthly Pass] MaxFee --> ApplyPricing ApplyPricing --> EVCheck{Electric Vehicle<br/>Used Charging?} EVCheck -->|Yes| AddChargingFee[โก Add Charging Fee<br/>Based on kWh Used] EVCheck -->|No| CalcTotal[๐งฎ Calculate Total Fee] AddChargingFee --> CalcTotal CalcTotal --> DisplayFee[๐บ Display Fee to Customer] DisplayFee --> PaymentMethod{Select Payment<br/>Method} PaymentMethod -->|๐ณ Card| ProcessCard[๐ณ Process Card Payment<br/>via Payment Gateway] PaymentMethod -->|๐ฑ Mobile| ProcessMobile[๐ฑ Process Mobile Payment] PaymentMethod -->|๐ต Cash| ProcessCash[๐ Attendant Collects Cash] ProcessCard --> PaymentSuccess{Payment<br/>Successful?} ProcessMobile --> PaymentSuccess ProcessCash --> PaymentSuccess PaymentSuccess -->|No| RetryPayment{Retry?} RetryPayment -->|Yes| PaymentMethod RetryPayment -->|No| BlockExit[๐ซ Keep Barrier Closed] BlockExit --> End1([โ Exit Denied]) PaymentSuccess -->|Yes| UpdateTicket[โ Mark Ticket as PAID] UpdateTicket --> ReleaseSpot[๐ ฟ๏ธ Release Parking Spot<br/>Update State to AVAILABLE] ReleaseSpot --> UpdateDisplay[๐บ Update Display Board<br/>Increment Available Count] UpdateDisplay --> IssueReceipt[๐งพ Generate & Print Receipt] IssueReceipt --> LogTransaction[๐ Log Transaction in Audit] LogTransaction --> OpenExitBarrier[๐ง Open Exit Barrier] OpenExitBarrier --> VehicleExits[๐ Vehicle Exits] VehicleExits --> CloseBarrier[๐ง Close Barrier After 10s] CloseBarrier --> End2([โ Exit Complete]) style Start fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style ScanTicket fill:#b2dfdb,stroke:#00695c,stroke-width:2px style TicketValid fill:#fff9c4,stroke:#f57f17,stroke-width:2px style LostTicket fill:#ffccbc,stroke:#d84315,stroke-width:2px style CalcDuration fill:#bbdefb,stroke:#1976d2,stroke-width:2px style CheckPeak fill:#e1bee7,stroke:#7b1fa2,stroke-width:2px style ApplyPricing fill:#c8e6c9,stroke:#388e3c,stroke-width:2px style EVCheck fill:#fff59d,stroke:#fbc02d,stroke-width:2px style AddChargingFee fill:#c5e1a5,stroke:#558b2f,stroke-width:2px style CalcTotal fill:#dcedc8,stroke:#689f38,stroke-width:2px style DisplayFee fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px style PaymentMethod fill:#fff9c4,stroke:#f57f17,stroke-width:2px style ProcessCard fill:#b3e5fc,stroke:#0277bd,stroke-width:2px style ProcessMobile fill:#ce93d8,stroke:#6a1b9a,stroke-width:2px style ProcessCash fill:#a5d6a7,stroke:#388e3c,stroke-width:2px style PaymentSuccess fill:#fff59d,stroke:#f9a825,stroke-width:2px style UpdateTicket fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style ReleaseSpot fill:#bbdefb,stroke:#1565c0,stroke-width:2px style UpdateDisplay fill:#f8bbd0,stroke:#c2185b,stroke-width:2px style IssueReceipt fill:#dcedc8,stroke:#558b2f,stroke-width:2px style LogTransaction fill:#d1c4e9,stroke:#5e35b1,stroke-width:2px style OpenExitBarrier fill:#b2dfdb,stroke:#00796b,stroke-width:2px style VehicleExits fill:#a5d6a7,stroke:#388e3c,stroke-width:2px style End1 fill:#ef5350,stroke:#c62828,stroke-width:3px style End2 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px style BlockExit fill:#ffcdd2,stroke:#d32f2f,stroke-width:2px
3. Reservation Creation and Management Flow
flowchart TD Start([๐ฑ Customer Opens App]) --> Login[๐ Customer Login] Login --> SelectDateTime[๐ Select Reservation<br/>Date & Time Range] SelectDateTime --> SelectVehicle[๐ Select Vehicle Type] SelectVehicle --> CheckAvail[๐ Check Spot Availability<br/>for Selected Time] CheckAvail --> SpotsAvail{Spots<br/>Available?} SpotsAvail -->|No| DisplayNoSpots[โ Display No Availability] DisplayNoSpots --> SuggestAlt[๐ก Suggest Alternative<br/>Time Slots] SuggestAlt --> AcceptAlt{Accept<br/>Alternative?} AcceptAlt -->|Yes| SelectDateTime AcceptAlt -->|No| End1([๐ซ Reservation Cancelled]) SpotsAvail -->|Yes| ReserveSpot[โ Reserve Spot<br/>Mark as RESERVED] ReserveSpot --> CalcResFee[๐ฐ Calculate Reservation Fee] CalcResFee --> ProcessPayment[๐ณ Process Advance Payment] ProcessPayment --> PaymentOK{Payment<br/>Success?} PaymentOK -->|No| ReleaseSpot[โ Release Reserved Spot] ReleaseSpot --> End2([๐ซ Reservation Failed]) PaymentOK -->|Yes| GenerateResID[๐ซ Generate Reservation ID] GenerateResID --> SendConfirm[๐ง Send Confirmation Email/SMS<br/>with QR Code] SendConfirm --> SetExpiry[โฐ Set Reservation Expiry<br/>15 min grace period] SetExpiry --> AddToCalendar[๐ Add to Customer Calendar] AddToCalendar --> End3([โ Reservation Confirmed]) %% Expiry Check Flow SetExpiry -.->|Background Process| MonitorExpiry[โฑ๏ธ Monitor Reservation Time] MonitorExpiry --> TimeCheck{Customer<br/>Arrived?} TimeCheck -->|Yes| ValidateEntry[โ Validate at Entry Gate] ValidateEntry --> ConvertToTicket[๐ซ Convert to Active Ticket] ConvertToTicket --> End4([โ Checked In]) TimeCheck -->|No, Expired| AutoCancel[โ ๏ธ Auto-Cancel Reservation] AutoCancel --> RefundFee[๐ธ Process Refund<br/>Per Cancellation Policy] RefundFee --> ReleaseSpot2[๐ ฟ๏ธ Release Spot] ReleaseSpot2 --> NotifyCustomer[๐ง Send Cancellation Notice] NotifyCustomer --> End5([โ Reservation Expired]) style Start fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style Login fill:#b2dfdb,stroke:#00695c,stroke-width:2px style SelectDateTime fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style SelectVehicle fill:#fff9c4,stroke:#f57f17,stroke-width:2px style CheckAvail fill:#bbdefb,stroke:#1976d2,stroke-width:2px style SpotsAvail fill:#fff59d,stroke:#f9a825,stroke-width:2px style DisplayNoSpots fill:#ffccbc,stroke:#d84315,stroke-width:2px style SuggestAlt fill:#e1bee7,stroke:#8e24aa,stroke-width:2px style ReserveSpot fill:#c8e6c9,stroke:#388e3c,stroke-width:2px style CalcResFee fill:#dcedc8,stroke:#558b2f,stroke-width:2px style ProcessPayment fill:#b3e5fc,stroke:#0277bd,stroke-width:2px style PaymentOK fill:#fff59d,stroke:#fbc02d,stroke-width:2px style GenerateResID fill:#c5cae9,stroke:#3949ab,stroke-width:2px style SendConfirm fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style SetExpiry fill:#ffe082,stroke:#f9a825,stroke-width:2px style AddToCalendar fill:#f8bbd0,stroke:#c2185b,stroke-width:2px style MonitorExpiry fill:#d1c4e9,stroke:#5e35b1,stroke-width:2px style AutoCancel fill:#ffab91,stroke:#d84315,stroke-width:2px style RefundFee fill:#fff59d,stroke:#fbc02d,stroke-width:2px style End1 fill:#ef5350,stroke:#c62828,stroke-width:3px style End2 fill:#ef5350,stroke:#c62828,stroke-width:3px style End3 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px style End4 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px style End5 fill:#ff8a65,stroke:#d84315,stroke-width:3px
Implementation
Java Implementation
package parkinglot;
import java.time.*;
import java.util.*;
import java.util.concurrent.*;
import java.math.BigDecimal;
// ============== Enumerations ==============
enum VehicleType {
MOTORCYCLE, CAR, TRUCK, ELECTRIC_CAR, VAN
}
enum SpotType {
MOTORCYCLE, COMPACT, STANDARD, LARGE, ELECTRIC
}
enum SpotState {
AVAILABLE, OCCUPIED, RESERVED, MAINTENANCE, OUT_OF_SERVICE
}
enum TicketStatus {
ACTIVE, PAID, LOST, RESERVED
}
enum PaymentMethod {
CASH, CREDIT_CARD, DEBIT_CARD, MOBILE_PAYMENT
}
enum PaymentStatus {
PENDING, COMPLETED, FAILED, REFUNDED
}
enum ReservationStatus {
CONFIRMED, ACTIVE, COMPLETED, CANCELLED, EXPIRED
}
// ============== Value Objects ==============
class Money {
private final BigDecimal amount;
private final String currency;
public Money(double amount) {
this(BigDecimal.valueOf(amount), "USD");
}
public Money(BigDecimal amount, String currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(this.amount.add(other.amount), this.currency);
}
public Money multiply(double multiplier) {
return new Money(this.amount.multiply(BigDecimal.valueOf(multiplier)), this.currency);
}
public BigDecimal getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
@Override
public String toString() {
return currency + " " + amount.setScale(2, BigDecimal.ROUND_HALF_UP);
}
}
class Address {
private final String street;
private final String city;
private final String state;
private final String zipCode;
private final String country;
public Address(String street, String city, String state, String zipCode, String country) {
this.street = street;
this.city = city;
this.state = state;
this.zipCode = zipCode;
this.country = country;
}
public String getFullAddress() {
return street + ", " + city + ", " + state + " " + zipCode + ", " + country;
}
}
// ============== Vehicle Hierarchy ==============
abstract class Vehicle {
protected final String licensePlate;
protected final VehicleType type;
public Vehicle(String licensePlate, VehicleType type) {
this.licensePlate = licensePlate;
this.type = type;
}
public String getLicensePlate() {
return licensePlate;
}
public VehicleType getType() {
return type;
}
}
class Motorcycle extends Vehicle {
public Motorcycle(String licensePlate) {
super(licensePlate, VehicleType.MOTORCYCLE);
}
}
class Car extends Vehicle {
public Car(String licensePlate) {
super(licensePlate, VehicleType.CAR);
}
}
class Truck extends Vehicle {
public Truck(String licensePlate) {
super(licensePlate, VehicleType.TRUCK);
}
}
class ElectricCar extends Vehicle {
private int batteryCapacity; // in kWh
private int currentCharge; // percentage
public ElectricCar(String licensePlate, int batteryCapacity) {
super(licensePlate, VehicleType.ELECTRIC_CAR);
this.batteryCapacity = batteryCapacity;
this.currentCharge = 50; // assume 50% when entering
}
public boolean needsCharging() {
return currentCharge < 80;
}
public int getBatteryCapacity() {
return batteryCapacity;
}
public void setCurrentCharge(int charge) {
this.currentCharge = Math.min(100, Math.max(0, charge));
}
}
// ============== Parking Spot Hierarchy ==============
abstract class ParkingSpot {
protected final String spotId;
protected final SpotType spotType;
protected SpotState state;
protected Vehicle vehicle;
protected final int floorNumber;
public ParkingSpot(String spotId, SpotType spotType, int floorNumber) {
this.spotId = spotId;
this.spotType = spotType;
this.floorNumber = floorNumber;
this.state = SpotState.AVAILABLE;
}
public abstract boolean canFitVehicle(Vehicle vehicle);
public synchronized boolean isAvailable() {
return state == SpotState.AVAILABLE;
}
public synchronized void assignVehicle(Vehicle vehicle) {
if (!isAvailable()) {
throw new IllegalStateException("Spot is not available");
}
if (!canFitVehicle(vehicle)) {
throw new IllegalArgumentException("Vehicle cannot fit in this spot");
}
this.vehicle = vehicle;
this.state = SpotState.OCCUPIED;
}
public synchronized void releaseVehicle() {
this.vehicle = null;
this.state = SpotState.AVAILABLE;
}
public synchronized void reserve() {
if (state != SpotState.AVAILABLE) {
throw new IllegalStateException("Cannot reserve non-available spot");
}
this.state = SpotState.RESERVED;
}
public void setState(SpotState state) {
this.state = state;
}
public String getSpotId() {
return spotId;
}
public SpotType getSpotType() {
return spotType;
}
public SpotState getState() {
return state;
}
public Vehicle getVehicle() {
return vehicle;
}
public int getFloorNumber() {
return floorNumber;
}
}
class CompactSpot extends ParkingSpot {
public CompactSpot(String spotId, int floorNumber) {
super(spotId, SpotType.COMPACT, floorNumber);
}
@Override
public boolean canFitVehicle(Vehicle vehicle) {
return vehicle.getType() == VehicleType.CAR ||
vehicle.getType() == VehicleType.MOTORCYCLE;
}
}
class StandardSpot extends ParkingSpot {
public StandardSpot(String spotId, int floorNumber) {
super(spotId, SpotType.STANDARD, floorNumber);
}
@Override
public boolean canFitVehicle(Vehicle vehicle) {
return vehicle.getType() == VehicleType.CAR ||
vehicle.getType() == VehicleType.MOTORCYCLE ||
vehicle.getType() == VehicleType.VAN;
}
}
class LargeSpot extends ParkingSpot {
public LargeSpot(String spotId, int floorNumber) {
super(spotId, SpotType.LARGE, floorNumber);
}
@Override
public boolean canFitVehicle(Vehicle vehicle) {
return true; // Can fit any vehicle
}
}
class MotorcycleSpot extends ParkingSpot {
public MotorcycleSpot(String spotId, int floorNumber) {
super(spotId, SpotType.MOTORCYCLE, floorNumber);
}
@Override
public boolean canFitVehicle(Vehicle vehicle) {
return vehicle.getType() == VehicleType.MOTORCYCLE;
}
}
class ElectricSpot extends ParkingSpot {
private ChargingStation chargingStation;
public ElectricSpot(String spotId, int floorNumber) {
super(spotId, SpotType.ELECTRIC, floorNumber);
this.chargingStation = new ChargingStation(spotId + "-CHARGER");
}
@Override
public boolean canFitVehicle(Vehicle vehicle) {
return vehicle.getType() == VehicleType.ELECTRIC_CAR;
}
public void startCharging(ElectricCar car) {
chargingStation.startCharging(car);
}
public void stopCharging() {
chargingStation.stopCharging();
}
public Money getChargingFee() {
return chargingStation.calculateChargingFee();
}
public ChargingStation getChargingStation() {
return chargingStation;
}
}
// ============== Charging Station ==============
class ChargingStation {
private final String stationId;
private boolean isCharging;
private LocalDateTime chargingStartTime;
private int kWhCharged;
private static final Money RATE_PER_KWH = new Money(0.30); // $0.30 per kWh
public ChargingStation(String stationId) {
this.stationId = stationId;
this.isCharging = false;
this.kWhCharged = 0;
}
public void startCharging(ElectricCar car) {
this.isCharging = true;
this.chargingStartTime = LocalDateTime.now();
// Simulate charging based on time
}
public void stopCharging() {
if (isCharging) {
Duration duration = Duration.between(chargingStartTime, LocalDateTime.now());
// Assume 7 kW charger
this.kWhCharged = (int) (duration.toHours() * 7);
this.isCharging = false;
}
}
public Money calculateChargingFee() {
return RATE_PER_KWH.multiply(kWhCharged);
}
public String getStationId() {
return stationId;
}
public int getKWhCharged() {
return kWhCharged;
}
}
// ============== Ticket ==============
class Ticket {
private final String ticketId;
private final LocalDateTime issueTime;
private ParkingSpot assignedSpot;
private final Vehicle vehicle;
private TicketStatus status;
private LocalDateTime paidTime;
public Ticket(Vehicle vehicle, ParkingSpot spot) {
this.ticketId = UUID.randomUUID().toString();
this.issueTime = LocalDateTime.now();
this.assignedSpot = spot;
this.vehicle = vehicle;
this.status = TicketStatus.ACTIVE;
}
public Duration getParkedDuration() {
LocalDateTime endTime = (paidTime != null) ? paidTime : LocalDateTime.now();
return Duration.between(issueTime, endTime);
}
public void markPaid() {
this.status = TicketStatus.PAID;
this.paidTime = LocalDateTime.now();
}
public void markLost() {
this.status = TicketStatus.LOST;
}
// Getters
public String getTicketId() { return ticketId; }
public LocalDateTime getIssueTime() { return issueTime; }
public ParkingSpot getAssignedSpot() { return assignedSpot; }
public Vehicle getVehicle() { return vehicle; }
public TicketStatus getStatus() { return status; }
public LocalDateTime getPaidTime() { return paidTime; }
}
// ============== Pricing Strategy Pattern ==============
interface PricingStrategy {
Money calculateFee(Ticket ticket);
}
class HourlyPricing implements PricingStrategy {
private Map<VehicleType, Money> hourlyRates;
private Money maxDailyRate;
public HourlyPricing() {
this.hourlyRates = new EnumMap<>(VehicleType.class);
hourlyRates.put(VehicleType.MOTORCYCLE, new Money(2.0));
hourlyRates.put(VehicleType.CAR, new Money(5.0));
hourlyRates.put(VehicleType.ELECTRIC_CAR, new Money(5.0));
hourlyRates.put(VehicleType.TRUCK, new Money(10.0));
hourlyRates.put(VehicleType.VAN, new Money(7.0));
this.maxDailyRate = new Money(50.0);
}
@Override
public Money calculateFee(Ticket ticket) {
Duration duration = ticket.getParkedDuration();
long hours = duration.toHours();
if (duration.toMinutes() % 60 > 0) {
hours++; // Round up partial hours
}
Money hourlyRate = hourlyRates.get(ticket.getVehicle().getType());
Money totalFee = hourlyRate.multiply(hours);
// Apply daily cap
if (totalFee.getAmount().compareTo(maxDailyRate.getAmount()) > 0) {
return maxDailyRate;
}
return totalFee;
}
public void updateRate(VehicleType type, Money newRate) {
hourlyRates.put(type, newRate);
}
}
class PeakHourPricing implements PricingStrategy {
private final PricingStrategy baseStrategy;
private final double peakMultiplier;
private final LocalTime peakStartMorning = LocalTime.of(7, 0);
private final LocalTime peakEndMorning = LocalTime.of(10, 0);
private final LocalTime peakStartEvening = LocalTime.of(16, 0);
private final LocalTime peakEndEvening = LocalTime.of(19, 0);
public PeakHourPricing(PricingStrategy baseStrategy, double peakMultiplier) {
this.baseStrategy = baseStrategy;
this.peakMultiplier = peakMultiplier;
}
private boolean isPeakHour(LocalDateTime time) {
LocalTime timeOfDay = time.toLocalTime();
return (timeOfDay.isAfter(peakStartMorning) && timeOfDay.isBefore(peakEndMorning)) ||
(timeOfDay.isAfter(peakStartEvening) && timeOfDay.isBefore(peakEndEvening));
}
@Override
public Money calculateFee(Ticket ticket) {
Money baseFee = baseStrategy.calculateFee(ticket);
// Check if parked during peak hours
if (isPeakHour(ticket.getIssueTime()) ||
(ticket.getPaidTime() != null && isPeakHour(ticket.getPaidTime()))) {
return baseFee.multiply(peakMultiplier);
}
return baseFee;
}
}
class MonthlyPassPricing implements PricingStrategy {
private Map<String, MonthlyPass> activePasses;
private PricingStrategy fallbackStrategy;
public MonthlyPassPricing(PricingStrategy fallbackStrategy) {
this.activePasses = new ConcurrentHashMap<>();
this.fallbackStrategy = fallbackStrategy;
}
@Override
public Money calculateFee(Ticket ticket) {
String licensePlate = ticket.getVehicle().getLicensePlate();
MonthlyPass pass = activePasses.get(licensePlate);
if (pass != null && pass.isValid()) {
return new Money(0.0); // Free for monthly pass holders
}
return fallbackStrategy.calculateFee(ticket);
}
public void addPass(MonthlyPass pass) {
activePasses.put(pass.getLicensePlate(), pass);
}
public void removePass(String licensePlate) {
activePasses.remove(licensePlate);
}
}
class MonthlyPass {
private final String passId;
private final String licensePlate;
private final LocalDate startDate;
private final LocalDate endDate;
public MonthlyPass(String licensePlate, LocalDate startDate) {
this.passId = UUID.randomUUID().toString();
this.licensePlate = licensePlate;
this.startDate = startDate;
this.endDate = startDate.plusMonths(1);
}
public boolean isValid() {
LocalDate today = LocalDate.now();
return !today.isBefore(startDate) && !today.isAfter(endDate);
}
public String getLicensePlate() {
return licensePlate;
}
}
// ============== Payment ==============
class Payment {
private final String paymentId;
private final Money amount;
private final PaymentMethod method;
private PaymentStatus status;
private final LocalDateTime timestamp;
private String transactionRef;
public Payment(Money amount, PaymentMethod method) {
this.paymentId = UUID.randomUUID().toString();
this.amount = amount;
this.method = method;
this.status = PaymentStatus.PENDING;
this.timestamp = LocalDateTime.now();
}
public boolean process() {
// Simulate payment processing
try {
Thread.sleep(100); // Simulate network delay
// 95% success rate for simulation
if (Math.random() < 0.95) {
this.status = PaymentStatus.COMPLETED;
this.transactionRef = "TXN-" + UUID.randomUUID().toString().substring(0, 8);
return true;
} else {
this.status = PaymentStatus.FAILED;
return false;
}
} catch (InterruptedException e) {
this.status = PaymentStatus.FAILED;
return false;
}
}
public void refund() {
if (status == PaymentStatus.COMPLETED) {
this.status = PaymentStatus.REFUNDED;
}
}
// Getters
public String getPaymentId() { return paymentId; }
public Money getAmount() { return amount; }
public PaymentMethod getMethod() { return method; }
public PaymentStatus getStatus() { return status; }
public String getTransactionRef() { return transactionRef; }
}
class Receipt {
private final String receiptId;
private final Ticket ticket;
private final Payment payment;
private final Money parkingFee;
private final Money chargingFee;
private final Money totalFee;
private final LocalDateTime issueTime;
public Receipt(Ticket ticket, Payment payment, Money parkingFee, Money chargingFee) {
this.receiptId = UUID.randomUUID().toString();
this.ticket = ticket;
this.payment = payment;
this.parkingFee = parkingFee;
this.chargingFee = chargingFee;
this.totalFee = parkingFee.add(chargingFee);
this.issueTime = LocalDateTime.now();
}
public String generateReceipt() {
StringBuilder sb = new StringBuilder();
sb.append("========== PARKING RECEIPT ==========\n");
sb.append("Receipt ID: ").append(receiptId).append("\n");
sb.append("Ticket ID: ").append(ticket.getTicketId()).append("\n");
sb.append("Vehicle: ").append(ticket.getVehicle().getLicensePlate()).append("\n");
sb.append("Entry Time: ").append(ticket.getIssueTime()).append("\n");
sb.append("Exit Time: ").append(ticket.getPaidTime()).append("\n");
sb.append("Duration: ").append(ticket.getParkedDuration().toHours()).append(" hours\n");
sb.append("Parking Fee: ").append(parkingFee).append("\n");
if (chargingFee.getAmount().compareTo(BigDecimal.ZERO) > 0) {
sb.append("Charging Fee: ").append(chargingFee).append("\n");
}
sb.append("Total: ").append(totalFee).append("\n");
sb.append("Payment Method: ").append(payment.getMethod()).append("\n");
sb.append("Transaction Ref: ").append(payment.getTransactionRef()).append("\n");
sb.append("====================================\n");
return sb.toString();
}
}
// ============== Display Board (Observer Pattern) ==============
class DisplayBoard {
private final int floorNumber;
private Map<SpotType, Integer> availableCounts;
public DisplayBoard(int floorNumber) {
this.floorNumber = floorNumber;
this.availableCounts = new EnumMap<>(SpotType.class);
for (SpotType type : SpotType.values()) {
availableCounts.put(type, 0);
}
}
public synchronized void update(SpotType spotType, int availableCount) {
availableCounts.put(spotType, availableCount);
}
public void displayAvailability() {
System.out.println("========== FLOOR " + floorNumber + " ==========");
for (Map.Entry<SpotType, Integer> entry : availableCounts.entrySet()) {
String icon = getIconForSpotType(entry.getKey());
System.out.println(icon + " " + entry.getKey() + ": " + entry.getValue() + " available");
}
System.out.println("================================");
}
private String getIconForSpotType(SpotType type) {
switch (type) {
case MOTORCYCLE: return "๐๏ธ";
case COMPACT: return "๐";
case STANDARD: return "๐";
case LARGE: return "๐";
case ELECTRIC: return "โก";
default: return "๐
ฟ๏ธ";
}
}
}
// ============== Parking Floor ==============
class ParkingFloor {
private final int floorNumber;
private Map<SpotType, List<ParkingSpot>> spotsByType;
private DisplayBoard displayBoard;
public ParkingFloor(int floorNumber) {
this.floorNumber = floorNumber;
this.spotsByType = new EnumMap<>(SpotType.class);
for (SpotType type : SpotType.values()) {
spotsByType.put(type, new CopyOnWriteArrayList<>());
}
this.displayBoard = new DisplayBoard(floorNumber);
}
public void addSpot(ParkingSpot spot) {
spotsByType.get(spot.getSpotType()).add(spot);
updateDisplayBoard();
}
public void removeSpot(String spotId) {
for (List<ParkingSpot> spots : spotsByType.values()) {
spots.removeIf(spot -> spot.getSpotId().equals(spotId));
}
updateDisplayBoard();
}
public synchronized ParkingSpot findAvailableSpot(VehicleType vehicleType) {
// Priority order for finding spots
List<SpotType> preferredTypes = getPreferredSpotTypes(vehicleType);
for (SpotType spotType : preferredTypes) {
List<ParkingSpot> spots = spotsByType.get(spotType);
for (ParkingSpot spot : spots) {
if (spot.isAvailable() && spot.canFitVehicle(new Car("TEMP"))) {
return spot;
}
}
}
return null; // No available spot
}
private List<SpotType> getPreferredSpotTypes(VehicleType vehicleType) {
List<SpotType> preferred = new ArrayList<>();
switch (vehicleType) {
case MOTORCYCLE:
preferred.add(SpotType.MOTORCYCLE);
preferred.add(SpotType.COMPACT);
break;
case CAR:
preferred.add(SpotType.COMPACT);
preferred.add(SpotType.STANDARD);
break;
case ELECTRIC_CAR:
preferred.add(SpotType.ELECTRIC);
preferred.add(SpotType.STANDARD);
break;
case VAN:
preferred.add(SpotType.STANDARD);
preferred.add(SpotType.LARGE);
break;
case TRUCK:
preferred.add(SpotType.LARGE);
break;
}
return preferred;
}
public int getAvailableCount(SpotType spotType) {
return (int) spotsByType.get(spotType).stream()
.filter(ParkingSpot::isAvailable)
.count();
}
public void updateDisplayBoard() {
for (SpotType type : SpotType.values()) {
int available = getAvailableCount(type);
displayBoard.update(type, available);
}
}
public int getFloorNumber() {
return floorNumber;
}
public DisplayBoard getDisplayBoard() {
return displayBoard;
}
}
// ============== Reservation ==============
class Reservation {
private final String reservationId;
private final Vehicle vehicle;
private final LocalDateTime startTime;
private final LocalDateTime endTime;
private ParkingSpot reservedSpot;
private ReservationStatus status;
public Reservation(Vehicle vehicle, LocalDateTime startTime, LocalDateTime endTime, ParkingSpot spot) {
this.reservationId = UUID.randomUUID().toString();
this.vehicle = vehicle;
this.startTime = startTime;
this.endTime = endTime;
this.reservedSpot = spot;
this.status = ReservationStatus.CONFIRMED;
}
public boolean isValid() {
LocalDateTime now = LocalDateTime.now();
return status == ReservationStatus.CONFIRMED &&
!now.isBefore(startTime) &&
!now.isAfter(endTime.plusMinutes(15)); // 15 min grace period
}
public boolean isExpired() {
return LocalDateTime.now().isAfter(endTime.plusMinutes(15));
}
public void cancel() {
this.status = ReservationStatus.CANCELLED;
if (reservedSpot != null) {
reservedSpot.setState(SpotState.AVAILABLE);
}
}
public void activate() {
this.status = ReservationStatus.ACTIVE;
}
public void complete() {
this.status = ReservationStatus.COMPLETED;
}
// Getters
public String getReservationId() { return reservationId; }
public Vehicle getVehicle() { return vehicle; }
public ParkingSpot getReservedSpot() { return reservedSpot; }
public ReservationStatus getStatus() { return status; }
public LocalDateTime getStartTime() { return startTime; }
public LocalDateTime getEndTime() { return endTime; }
}
class ReservationManager {
private Map<String, Reservation> reservations;
public ReservationManager() {
this.reservations = new ConcurrentHashMap<>();
startExpiryMonitor();
}
public Reservation createReservation(Vehicle vehicle, LocalDateTime startTime,
LocalDateTime endTime, ParkingSpot spot) {
spot.reserve();
Reservation reservation = new Reservation(vehicle, startTime, endTime, spot);
reservations.put(reservation.getReservationId(), reservation);
return reservation;
}
public boolean validateReservation(String reservationId) {
Reservation reservation = reservations.get(reservationId);
return reservation != null && reservation.isValid();
}
public Reservation getReservation(String reservationId) {
return reservations.get(reservationId);
}
public void cancelReservation(String reservationId) {
Reservation reservation = reservations.get(reservationId);
if (reservation != null) {
reservation.cancel();
}
}
private void startExpiryMonitor() {
// Background task to check for expired reservations
Thread monitor = new Thread(() -> {
while (true) {
try {
Thread.sleep(60000); // Check every minute
LocalDateTime now = LocalDateTime.now();
for (Reservation reservation : reservations.values()) {
if (reservation.isExpired() &&
reservation.getStatus() == ReservationStatus.CONFIRMED) {
reservation.cancel();
System.out.println("Reservation " + reservation.getReservationId() +
" expired and cancelled");
}
}
} catch (InterruptedException e) {
break;
}
}
});
monitor.setDaemon(true);
monitor.start();
}
}
// ============== Entry and Exit Gates ==============
class EntranceGate {
private final String gateId;
private final ParkingLot parkingLot;
public EntranceGate(String gateId, ParkingLot parkingLot) {
this.gateId = gateId;
this.parkingLot = parkingLot;
}
public Ticket issueTicket(Vehicle vehicle, String reservationId) {
// Check reservation first
if (reservationId != null) {
if (!parkingLot.getReservationManager().validateReservation(reservationId)) {
throw new IllegalStateException("Invalid or expired reservation");
}
Reservation reservation = parkingLot.getReservationManager().getReservation(reservationId);
reservation.activate();
Ticket ticket = new Ticket(vehicle, reservation.getReservedSpot());
reservation.getReservedSpot().assignVehicle(vehicle);
return ticket;
}
// Find available spot
ParkingSpot spot = parkingLot.getAvailableSpot(vehicle.getType());
if (spot == null) {
throw new IllegalStateException("No available spots for vehicle type: " + vehicle.getType());
}
spot.assignVehicle(vehicle);
Ticket ticket = new Ticket(vehicle, spot);
parkingLot.addActiveTicket(ticket);
// Update display boards
parkingLot.updateAllDisplayBoards();
System.out.println("โ
Ticket issued: " + ticket.getTicketId() +
" | Spot: " + spot.getSpotId() +
" | Floor: " + spot.getFloorNumber());
return ticket;
}
public String getGateId() {
return gateId;
}
}
class ExitGate {
private final String gateId;
private final ParkingLot parkingLot;
public ExitGate(String gateId, ParkingLot parkingLot) {
this.gateId = gateId;
this.parkingLot = parkingLot;
}
public Receipt processExit(String ticketId, PaymentMethod paymentMethod) {
Ticket ticket = parkingLot.getActiveTicket(ticketId);
if (ticket == null) {
throw new IllegalArgumentException("Invalid ticket ID");
}
// Calculate parking fee
Money parkingFee = parkingLot.getPricingStrategy().calculateFee(ticket);
// Calculate charging fee if applicable
Money chargingFee = new Money(0.0);
ParkingSpot spot = ticket.getAssignedSpot();
if (spot instanceof ElectricSpot) {
ElectricSpot electricSpot = (ElectricSpot) spot;
electricSpot.stopCharging();
chargingFee = electricSpot.getChargingFee();
}
Money totalFee = parkingFee.add(chargingFee);
// Process payment
Payment payment = new Payment(totalFee, paymentMethod);
boolean paymentSuccess = payment.process();
if (!paymentSuccess) {
throw new IllegalStateException("Payment failed");
}
// Mark ticket as paid and release spot
ticket.markPaid();
spot.releaseVehicle();
parkingLot.removeActiveTicket(ticketId);
// Update display boards
parkingLot.updateAllDisplayBoards();
// Generate receipt
Receipt receipt = new Receipt(ticket, payment, parkingFee, chargingFee);
System.out.println("โ
Exit processed successfully");
System.out.println(receipt.generateReceipt());
return receipt;
}
public String getGateId() {
return gateId;
}
}
// ============== Parking Lot (Singleton) ==============
class ParkingLot {
private static ParkingLot instance;
private final String name;
private final Address address;
private List<ParkingFloor> floors;
private List<EntranceGate> entranceGates;
private List<ExitGate> exitGates;
private PricingStrategy pricingStrategy;
private ReservationManager reservationManager;
private Map<String, Ticket> activeTickets;
private ParkingLot(String name, Address address) {
this.name = name;
this.address = address;
this.floors = new CopyOnWriteArrayList<>();
this.entranceGates = new CopyOnWriteArrayList<>();
this.exitGates = new CopyOnWriteArrayList<>();
this.pricingStrategy = new HourlyPricing();
this.reservationManager = new ReservationManager();
this.activeTickets = new ConcurrentHashMap<>();
}
public static synchronized ParkingLot getInstance(String name, Address address) {
if (instance == null) {
instance = new ParkingLot(name, address);
}
return instance;
}
public static ParkingLot getInstance() {
if (instance == null) {
throw new IllegalStateException("ParkingLot not initialized");
}
return instance;
}
public void addFloor(ParkingFloor floor) {
floors.add(floor);
}
public void addEntranceGate(EntranceGate gate) {
entranceGates.add(gate);
}
public void addExitGate(ExitGate gate) {
exitGates.add(gate);
}
public synchronized ParkingSpot getAvailableSpot(VehicleType vehicleType) {
for (ParkingFloor floor : floors) {
ParkingSpot spot = floor.findAvailableSpot(vehicleType);
if (spot != null) {
return spot;
}
}
return null;
}
public int getTotalCapacity() {
int total = 0;
for (ParkingFloor floor : floors) {
for (SpotType type : SpotType.values()) {
total += floor.getAvailableCount(type);
}
}
return total;
}
public int getAvailableCount(SpotType spotType) {
return floors.stream()
.mapToInt(floor -> floor.getAvailableCount(spotType))
.sum();
}
public void updateAllDisplayBoards() {
for (ParkingFloor floor : floors) {
floor.updateDisplayBoard();
}
}
public void displayAllFloors() {
for (ParkingFloor floor : floors) {
floor.getDisplayBoard().displayAvailability();
}
}
public void addActiveTicket(Ticket ticket) {
activeTickets.put(ticket.getTicketId(), ticket);
}
public void removeActiveTicket(String ticketId) {
activeTickets.remove(ticketId);
}
public Ticket getActiveTicket(String ticketId) {
return activeTickets.get(ticketId);
}
public void setPricingStrategy(PricingStrategy strategy) {
this.pricingStrategy = strategy;
}
public PricingStrategy getPricingStrategy() {
return pricingStrategy;
}
public ReservationManager getReservationManager() {
return reservationManager;
}
public List<EntranceGate> getEntranceGates() {
return entranceGates;
}
public List<ExitGate> getExitGates() {
return exitGates;
}
}
// ============== Admin and Attendant ==============
class Admin {
private final String adminId;
private final String name;
public Admin(String adminId, String name) {
this.adminId = adminId;
this.name = name;
}
public void addFloor(ParkingLot parkingLot, ParkingFloor floor) {
parkingLot.addFloor(floor);
System.out.println("Admin " + name + " added floor " + floor.getFloorNumber());
}
public void addSpot(ParkingFloor floor, ParkingSpot spot) {
floor.addSpot(spot);
System.out.println("Admin " + name + " added spot " + spot.getSpotId());
}
public void updatePricing(ParkingLot parkingLot, PricingStrategy strategy) {
parkingLot.setPricingStrategy(strategy);
System.out.println("Admin " + name + " updated pricing strategy");
}
public void markSpotMaintenance(ParkingSpot spot) {
spot.setState(SpotState.MAINTENANCE);
System.out.println("Admin " + name + " marked spot " + spot.getSpotId() + " for maintenance");
}
}
class ParkingAttendant {
private final String attendantId;
private final String name;
public ParkingAttendant(String attendantId, String name) {
this.attendantId = attendantId;
this.name = name;
}
public Receipt assistPayment(ExitGate exitGate, String ticketId, PaymentMethod method) {
System.out.println("Attendant " + name + " assisting with payment");
return exitGate.processExit(ticketId, method);
}
public Ticket handleLostTicket(ParkingLot parkingLot, Vehicle vehicle) {
System.out.println("Attendant " + name + " handling lost ticket for " + vehicle.getLicensePlate());
// Create a lost ticket with maximum fee assumption (24 hours)
ParkingSpot tempSpot = parkingLot.getAvailableSpot(vehicle.getType());
Ticket lostTicket = new Ticket(vehicle, tempSpot);
lostTicket.markLost();
return lostTicket;
}
}
// ============== Demo Class ==============
class ParkingLotDemo {
public static void main(String[] args) {
System.out.println("๐
ฟ๏ธ Initializing Parking Lot System...\n");
// Create parking lot
Address address = new Address("123 Main St", "San Francisco", "CA", "94101", "USA");
ParkingLot parkingLot = ParkingLot.getInstance("Downtown Parking", address);
// Create floors
ParkingFloor floor1 = new ParkingFloor(1);
ParkingFloor floor2 = new ParkingFloor(2);
// Add spots to floor 1
for (int i = 1; i <= 5; i++) {
floor1.addSpot(new CompactSpot("F1-C" + i, 1));
}
for (int i = 1; i <= 5; i++) {
floor1.addSpot(new StandardSpot("F1-S" + i, 1));
}
for (int i = 1; i <= 3; i++) {
floor1.addSpot(new MotorcycleSpot("F1-M" + i, 1));
}
for (int i = 1; i <= 2; i++) {
floor1.addSpot(new ElectricSpot("F1-E" + i, 1));
}
// Add spots to floor 2
for (int i = 1; i <= 5; i++) {
floor2.addSpot(new StandardSpot("F2-S" + i, 2));
}
for (int i = 1; i <= 3; i++) {
floor2.addSpot(new LargeSpot("F2-L" + i, 2));
}
// Add floors to parking lot
parkingLot.addFloor(floor1);
parkingLot.addFloor(floor2);
// Create gates
EntranceGate entrance1 = new EntranceGate("ENT-1", parkingLot);
ExitGate exit1 = new ExitGate("EXIT-1", parkingLot);
parkingLot.addEntranceGate(entrance1);
parkingLot.addExitGate(exit1);
// Display initial availability
System.out.println("๐ Initial Parking Availability:");
parkingLot.displayAllFloors();
System.out.println();
// Scenario 1: Regular car entry and exit
System.out.println("๐ Scenario 1: Car Entry and Exit\n");
Vehicle car1 = new Car("ABC-123");
Ticket ticket1 = entrance1.issueTicket(car1, null);
try {
Thread.sleep(2000); // Simulate 2 seconds parking
} catch (InterruptedException e) {
e.printStackTrace();
}
Receipt receipt1 = exit1.processExit(ticket1.getTicketId(), PaymentMethod.CREDIT_CARD);
System.out.println();
// Scenario 2: Electric car with charging
System.out.println("โก Scenario 2: Electric Car with Charging\n");
ElectricCar electricCar = new ElectricCar("TESLA-X", 75);
Ticket ticket2 = entrance1.issueTicket(electricCar, null);
// Start charging
if (ticket2.getAssignedSpot() instanceof ElectricSpot) {
ElectricSpot electricSpot = (ElectricSpot) ticket2.getAssignedSpot();
electricSpot.startCharging(electricCar);
System.out.println("โก Charging started at " + electricSpot.getSpotId());
}
try {
Thread.sleep(3000); // Simulate 3 seconds charging
} catch (InterruptedException e) {
e.printStackTrace();
}
Receipt receipt2 = exit1.processExit(ticket2.getTicketId(), PaymentMethod.MOBILE_PAYMENT);
System.out.println();
// Scenario 3: Reservation
System.out.println("๐
Scenario 3: Advance Reservation\n");
Vehicle car2 = new Car("XYZ-789");
LocalDateTime reservationStart = LocalDateTime.now();
LocalDateTime reservationEnd = reservationStart.plusHours(2);
ParkingSpot spotForReservation = parkingLot.getAvailableSpot(VehicleType.CAR);
Reservation reservation = parkingLot.getReservationManager()
.createReservation(car2, reservationStart, reservationEnd, spotForReservation);
System.out.println("โ
Reservation created: " + reservation.getReservationId());
System.out.println(" Spot: " + spotForReservation.getSpotId());
System.out.println(" Valid until: " + reservationEnd.plusMinutes(15));
System.out.println();
// Use reservation
Ticket ticket3 = entrance1.issueTicket(car2, reservation.getReservationId());
System.out.println("โ
Reservation activated, vehicle parked\n");
// Display final availability
System.out.println("๐ Final Parking Availability:");
parkingLot.displayAllFloors();
System.out.println("\nโ
Parking Lot System Demo Complete!");
}
}
Python Implementation
from enum import Enum
from dataclasses import dataclass
from typing import List, Dict, Optional, Protocol
from datetime import datetime, timedelta
from abc import ABC, abstractmethod
import uuid
import time
import threading
from decimal import Decimal
# ============== Enumerations ==============
class VehicleType(Enum):
MOTORCYCLE = "motorcycle"
CAR = "car"
TRUCK = "truck"
ELECTRIC_CAR = "electric_car"
VAN = "van"
class SpotType(Enum):
MOTORCYCLE = "motorcycle"
COMPACT = "compact"
STANDARD = "standard"
LARGE = "large"
ELECTRIC = "electric"
class SpotState(Enum):
AVAILABLE = "available"
OCCUPIED = "occupied"
RESERVED = "reserved"
MAINTENANCE = "maintenance"
OUT_OF_SERVICE = "out_of_service"
class TicketStatus(Enum):
ACTIVE = "active"
PAID = "paid"
LOST = "lost"
RESERVED = "reserved"
class PaymentMethod(Enum):
CASH = "cash"
CREDIT_CARD = "credit_card"
DEBIT_CARD = "debit_card"
MOBILE_PAYMENT = "mobile_payment"
class PaymentStatus(Enum):
PENDING = "pending"
COMPLETED = "completed"
FAILED = "failed"
REFUNDED = "refunded"
class ReservationStatus(Enum):
CONFIRMED = "confirmed"
ACTIVE = "active"
COMPLETED = "completed"
CANCELLED = "cancelled"
EXPIRED = "expired"
# ============== Value Objects ==============
@dataclass(frozen=True)
class Money:
amount: Decimal
currency: str = "USD"
def __post_init__(self):
if self.amount < 0:
raise ValueError("Amount cannot be negative")
def add(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError("Currency mismatch")
return Money(self.amount + other.amount, self.currency)
def multiply(self, multiplier: float) -> 'Money':
return Money(self.amount * Decimal(str(multiplier)), self.currency)
def __str__(self) -> str:
return f"{self.currency} {self.amount:.2f}"
@dataclass(frozen=True)
class Address:
street: str
city: str
state: str
zip_code: str
country: str
def get_full_address(self) -> str:
return f"{self.street}, {self.city}, {self.state} {self.zip_code}, {self.country}"
# ============== Vehicle Hierarchy ==============
class Vehicle(ABC):
def __init__(self, license_plate: str, vehicle_type: VehicleType):
self.license_plate = license_plate
self.vehicle_type = vehicle_type
def get_type(self) -> VehicleType:
return self.vehicle_type
class Motorcycle(Vehicle):
def __init__(self, license_plate: str):
super().__init__(license_plate, VehicleType.MOTORCYCLE)
class Car(Vehicle):
def __init__(self, license_plate: str):
super().__init__(license_plate, VehicleType.CAR)
class Truck(Vehicle):
def __init__(self, license_plate: str):
super().__init__(license_plate, VehicleType.TRUCK)
class ElectricCar(Vehicle):
def __init__(self, license_plate: str, battery_capacity: int):
super().__init__(license_plate, VehicleType.ELECTRIC_CAR)
self.battery_capacity = battery_capacity # kWh
self.current_charge = 50 # percentage
def needs_charging(self) -> bool:
return self.current_charge < 80
def set_current_charge(self, charge: int):
self.current_charge = max(0, min(100, charge))
# ============== Parking Spot Hierarchy ==============
class ParkingSpot(ABC):
def __init__(self, spot_id: str, spot_type: SpotType, floor_number: int):
self.spot_id = spot_id
self.spot_type = spot_type
self.floor_number = floor_number
self.state = SpotState.AVAILABLE
self.vehicle: Optional[Vehicle] = None
self._lock = threading.Lock()
@abstractmethod
def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
pass
def is_available(self) -> bool:
with self._lock:
return self.state == SpotState.AVAILABLE
def assign_vehicle(self, vehicle: Vehicle):
with self._lock:
if not self.is_available():
raise ValueError("Spot is not available")
if not self.can_fit_vehicle(vehicle):
raise ValueError("Vehicle cannot fit in this spot")
self.vehicle = vehicle
self.state = SpotState.OCCUPIED
def release_vehicle(self):
with self._lock:
self.vehicle = None
self.state = SpotState.AVAILABLE
def reserve(self):
with self._lock:
if self.state != SpotState.AVAILABLE:
raise ValueError("Cannot reserve non-available spot")
self.state = SpotState.RESERVED
def set_state(self, state: SpotState):
with self._lock:
self.state = state
class CompactSpot(ParkingSpot):
def __init__(self, spot_id: str, floor_number: int):
super().__init__(spot_id, SpotType.COMPACT, floor_number)
def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
return vehicle.vehicle_type in [VehicleType.CAR, VehicleType.MOTORCYCLE]
class StandardSpot(ParkingSpot):
def __init__(self, spot_id: str, floor_number: int):
super().__init__(spot_id, SpotType.STANDARD, floor_number)
def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
return vehicle.vehicle_type in [VehicleType.CAR, VehicleType.MOTORCYCLE, VehicleType.VAN]
class LargeSpot(ParkingSpot):
def __init__(self, spot_id: str, floor_number: int):
super().__init__(spot_id, SpotType.LARGE, floor_number)
def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
return True # Can fit any vehicle
class MotorcycleSpot(ParkingSpot):
def __init__(self, spot_id: str, floor_number: int):
super().__init__(spot_id, SpotType.MOTORCYCLE, floor_number)
def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
return vehicle.vehicle_type == VehicleType.MOTORCYCLE
class ChargingStation:
def __init__(self, station_id: str):
self.station_id = station_id
self.is_charging = False
self.charging_start_time: Optional[datetime] = None
self.kwh_charged = 0
self.rate_per_kwh = Money(Decimal("0.30")) # $0.30 per kWh
def start_charging(self, car: ElectricCar):
self.is_charging = True
self.charging_start_time = datetime.now()
def stop_charging(self):
if self.is_charging and self.charging_start_time:
duration = datetime.now() - self.charging_start_time
hours = duration.total_seconds() / 3600
self.kwh_charged = int(hours * 7) # Assume 7 kW charger
self.is_charging = False
def calculate_charging_fee(self) -> Money:
return self.rate_per_kwh.multiply(self.kwh_charged)
class ElectricSpot(ParkingSpot):
def __init__(self, spot_id: str, floor_number: int):
super().__init__(spot_id, SpotType.ELECTRIC, floor_number)
self.charging_station = ChargingStation(f"{spot_id}-CHARGER")
def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
return vehicle.vehicle_type == VehicleType.ELECTRIC_CAR
def start_charging(self, car: ElectricCar):
self.charging_station.start_charging(car)
def stop_charging(self):
self.charging_station.stop_charging()
def get_charging_fee(self) -> Money:
return self.charging_station.calculate_charging_fee()
# ============== Ticket ==============
class Ticket:
def __init__(self, vehicle: Vehicle, spot: ParkingSpot):
self.ticket_id = str(uuid.uuid4())
self.issue_time = datetime.now()
self.assigned_spot = spot
self.vehicle = vehicle
self.status = TicketStatus.ACTIVE
self.paid_time: Optional[datetime] = None
def get_parked_duration(self) -> timedelta:
end_time = self.paid_time if self.paid_time else datetime.now()
return end_time - self.issue_time
def mark_paid(self):
self.status = TicketStatus.PAID
self.paid_time = datetime.now()
def mark_lost(self):
self.status = TicketStatus.LOST
# ============== Pricing Strategy Pattern ==============
class PricingStrategy(Protocol):
def calculate_fee(self, ticket: Ticket) -> Money:
...
class HourlyPricing:
def __init__(self):
self.hourly_rates: Dict[VehicleType, Money] = {
VehicleType.MOTORCYCLE: Money(Decimal("2.0")),
VehicleType.CAR: Money(Decimal("5.0")),
VehicleType.ELECTRIC_CAR: Money(Decimal("5.0")),
VehicleType.TRUCK: Money(Decimal("10.0")),
VehicleType.VAN: Money(Decimal("7.0")),
}
self.max_daily_rate = Money(Decimal("50.0"))
def calculate_fee(self, ticket: Ticket) -> Money:
duration = ticket.get_parked_duration()
hours = duration.total_seconds() / 3600
if hours < 1:
hours = 1 # Minimum 1 hour
else:
hours = int(hours) + (1 if duration.total_seconds() % 3600 > 0 else 0)
hourly_rate = self.hourly_rates[ticket.vehicle.vehicle_type]
total_fee = hourly_rate.multiply(hours)
# Apply daily cap
if total_fee.amount > self.max_daily_rate.amount:
return self.max_daily_rate
return total_fee
def update_rate(self, vehicle_type: VehicleType, new_rate: Money):
self.hourly_rates[vehicle_type] = new_rate
class PeakHourPricing:
def __init__(self, base_strategy: PricingStrategy, peak_multiplier: float):
self.base_strategy = base_strategy
self.peak_multiplier = peak_multiplier
self.peak_hours = [
(7, 10), # 7 AM - 10 AM
(16, 19), # 4 PM - 7 PM
]
def is_peak_hour(self, dt: datetime) -> bool:
hour = dt.hour
return any(start <= hour < end for start, end in self.peak_hours)
def calculate_fee(self, ticket: Ticket) -> Money:
base_fee = self.base_strategy.calculate_fee(ticket)
if self.is_peak_hour(ticket.issue_time) or \
(ticket.paid_time and self.is_peak_hour(ticket.paid_time)):
return base_fee.multiply(self.peak_multiplier)
return base_fee
@dataclass
class MonthlyPass:
pass_id: str
license_plate: str
start_date: datetime
end_date: datetime
def is_valid(self) -> bool:
now = datetime.now()
return self.start_date <= now <= self.end_date
class MonthlyPassPricing:
def __init__(self, fallback_strategy: PricingStrategy):
self.active_passes: Dict[str, MonthlyPass] = {}
self.fallback_strategy = fallback_strategy
def calculate_fee(self, ticket: Ticket) -> Money:
license_plate = ticket.vehicle.license_plate
pass_obj = self.active_passes.get(license_plate)
if pass_obj and pass_obj.is_valid():
return Money(Decimal("0.0")) # Free for monthly pass holders
return self.fallback_strategy.calculate_fee(ticket)
def add_pass(self, monthly_pass: MonthlyPass):
self.active_passes[monthly_pass.license_plate] = monthly_pass
def remove_pass(self, license_plate: str):
self.active_passes.pop(license_plate, None)
# ============== Payment ==============
class Payment:
def __init__(self, amount: Money, method: PaymentMethod):
self.payment_id = str(uuid.uuid4())
self.amount = amount
self.method = method
self.status = PaymentStatus.PENDING
self.timestamp = datetime.now()
self.transaction_ref: Optional[str] = None
def process(self) -> bool:
# Simulate payment processing
time.sleep(0.1) # Simulate network delay
# 95% success rate for simulation
import random
if random.random() < 0.95:
self.status = PaymentStatus.COMPLETED
self.transaction_ref = f"TXN-{str(uuid.uuid4())[:8]}"
return True
else:
self.status = PaymentStatus.FAILED
return False
def refund(self):
if self.status == PaymentStatus.COMPLETED:
self.status = PaymentStatus.REFUNDED
class Receipt:
def __init__(self, ticket: Ticket, payment: Payment,
parking_fee: Money, charging_fee: Money):
self.receipt_id = str(uuid.uuid4())
self.ticket = ticket
self.payment = payment
self.parking_fee = parking_fee
self.charging_fee = charging_fee
self.total_fee = parking_fee.add(charging_fee)
self.issue_time = datetime.now()
def generate_receipt(self) -> str:
lines = [
"========== PARKING RECEIPT ==========",
f"Receipt ID: {self.receipt_id}",
f"Ticket ID: {self.ticket.ticket_id}",
f"Vehicle: {self.ticket.vehicle.license_plate}",
f"Entry Time: {self.ticket.issue_time}",
f"Exit Time: {self.ticket.paid_time}",
f"Duration: {self.ticket.get_parked_duration().total_seconds() / 3600:.1f} hours",
f"Parking Fee: {self.parking_fee}",
]
if self.charging_fee.amount > 0:
lines.append(f"Charging Fee: {self.charging_fee}")
lines.extend([
f"Total: {self.total_fee}",
f"Payment Method: {self.payment.method.value}",
f"Transaction Ref: {self.payment.transaction_ref}",
"====================================",
])
return "\n".join(lines)
# ============== Display Board (Observer Pattern) ==============
class DisplayBoard:
def __init__(self, floor_number: int):
self.floor_number = floor_number
self.available_counts: Dict[SpotType, int] = {
spot_type: 0 for spot_type in SpotType
}
def update(self, spot_type: SpotType, available_count: int):
self.available_counts[spot_type] = available_count
def display_availability(self):
icons = {
SpotType.MOTORCYCLE: "๐๏ธ",
SpotType.COMPACT: "๐",
SpotType.STANDARD: "๐",
SpotType.LARGE: "๐",
SpotType.ELECTRIC: "โก",
}
print(f"========== FLOOR {self.floor_number} ==========")
for spot_type, count in self.available_counts.items():
icon = icons.get(spot_type, "๐
ฟ๏ธ")
print(f"{icon} {spot_type.value.upper()}: {count} available")
print("================================")
# ============== Parking Floor ==============
class ParkingFloor:
def __init__(self, floor_number: int):
self.floor_number = floor_number
self.spots_by_type: Dict[SpotType, List[ParkingSpot]] = {
spot_type: [] for spot_type in SpotType
}
self.display_board = DisplayBoard(floor_number)
def add_spot(self, spot: ParkingSpot):
self.spots_by_type[spot.spot_type].append(spot)
self.update_display_board()
def remove_spot(self, spot_id: str):
for spots in self.spots_by_type.values():
spots[:] = [s for s in spots if s.spot_id != spot_id]
self.update_display_board()
def find_available_spot(self, vehicle_type: VehicleType) -> Optional[ParkingSpot]:
preferred_types = self._get_preferred_spot_types(vehicle_type)
for spot_type in preferred_types:
for spot in self.spots_by_type[spot_type]:
if spot.is_available():
return spot
return None
def _get_preferred_spot_types(self, vehicle_type: VehicleType) -> List[SpotType]:
preferences = {
VehicleType.MOTORCYCLE: [SpotType.MOTORCYCLE, SpotType.COMPACT],
VehicleType.CAR: [SpotType.COMPACT, SpotType.STANDARD],
VehicleType.ELECTRIC_CAR: [SpotType.ELECTRIC, SpotType.STANDARD],
VehicleType.VAN: [SpotType.STANDARD, SpotType.LARGE],
VehicleType.TRUCK: [SpotType.LARGE],
}
return preferences.get(vehicle_type, [])
def get_available_count(self, spot_type: SpotType) -> int:
return sum(1 for spot in self.spots_by_type[spot_type] if spot.is_available())
def update_display_board(self):
for spot_type in SpotType:
count = self.get_available_count(spot_type)
self.display_board.update(spot_type, count)
# ============== Reservation ==============
class Reservation:
def __init__(self, vehicle: Vehicle, start_time: datetime,
end_time: datetime, spot: ParkingSpot):
self.reservation_id = str(uuid.uuid4())
self.vehicle = vehicle
self.start_time = start_time
self.end_time = end_time
self.reserved_spot = spot
self.status = ReservationStatus.CONFIRMED
def is_valid(self) -> bool:
now = datetime.now()
grace_period = timedelta(minutes=15)
return (self.status == ReservationStatus.CONFIRMED and
self.start_time <= now <= self.end_time + grace_period)
def is_expired(self) -> bool:
grace_period = timedelta(minutes=15)
return datetime.now() > self.end_time + grace_period
def cancel(self):
self.status = ReservationStatus.CANCELLED
if self.reserved_spot:
self.reserved_spot.set_state(SpotState.AVAILABLE)
def activate(self):
self.status = ReservationStatus.ACTIVE
def complete(self):
self.status = ReservationStatus.COMPLETED
class ReservationManager:
def __init__(self):
self.reservations: Dict[str, Reservation] = {}
self._start_expiry_monitor()
def create_reservation(self, vehicle: Vehicle, start_time: datetime,
end_time: datetime, spot: ParkingSpot) -> Reservation:
spot.reserve()
reservation = Reservation(vehicle, start_time, end_time, spot)
self.reservations[reservation.reservation_id] = reservation
return reservation
def validate_reservation(self, reservation_id: str) -> bool:
reservation = self.reservations.get(reservation_id)
return reservation is not None and reservation.is_valid()
def get_reservation(self, reservation_id: str) -> Optional[Reservation]:
return self.reservations.get(reservation_id)
def cancel_reservation(self, reservation_id: str):
reservation = self.reservations.get(reservation_id)
if reservation:
reservation.cancel()
def _start_expiry_monitor(self):
def monitor():
while True:
time.sleep(60) # Check every minute
for reservation in list(self.reservations.values()):
if (reservation.is_expired() and
reservation.status == ReservationStatus.CONFIRMED):
reservation.cancel()
print(f"Reservation {reservation.reservation_id} expired and cancelled")
thread = threading.Thread(target=monitor, daemon=True)
thread.start()
# ============== Entry and Exit Gates ==============
class EntranceGate:
def __init__(self, gate_id: str, parking_lot: 'ParkingLot'):
self.gate_id = gate_id
self.parking_lot = parking_lot
def issue_ticket(self, vehicle: Vehicle, reservation_id: Optional[str] = None) -> Ticket:
# Check reservation first
if reservation_id:
if not self.parking_lot.reservation_manager.validate_reservation(reservation_id):
raise ValueError("Invalid or expired reservation")
reservation = self.parking_lot.reservation_manager.get_reservation(reservation_id)
reservation.activate()
ticket = Ticket(vehicle, reservation.reserved_spot)
reservation.reserved_spot.assign_vehicle(vehicle)
return ticket
# Find available spot
spot = self.parking_lot.get_available_spot(vehicle.vehicle_type)
if not spot:
raise ValueError(f"No available spots for vehicle type: {vehicle.vehicle_type}")
spot.assign_vehicle(vehicle)
ticket = Ticket(vehicle, spot)
self.parking_lot.add_active_ticket(ticket)
# Update display boards
self.parking_lot.update_all_display_boards()
print(f"โ
Ticket issued: {ticket.ticket_id} | "
f"Spot: {spot.spot_id} | Floor: {spot.floor_number}")
return ticket
class ExitGate:
def __init__(self, gate_id: str, parking_lot: 'ParkingLot'):
self.gate_id = gate_id
self.parking_lot = parking_lot
def process_exit(self, ticket_id: str, payment_method: PaymentMethod) -> Receipt:
ticket = self.parking_lot.get_active_ticket(ticket_id)
if not ticket:
raise ValueError("Invalid ticket ID")
# Calculate parking fee
parking_fee = self.parking_lot.pricing_strategy.calculate_fee(ticket)
# Calculate charging fee if applicable
charging_fee = Money(Decimal("0.0"))
spot = ticket.assigned_spot
if isinstance(spot, ElectricSpot):
spot.stop_charging()
charging_fee = spot.get_charging_fee()
total_fee = parking_fee.add(charging_fee)
# Process payment
payment = Payment(total_fee, payment_method)
if not payment.process():
raise ValueError("Payment failed")
# Mark ticket as paid and release spot
ticket.mark_paid()
spot.release_vehicle()
self.parking_lot.remove_active_ticket(ticket_id)
# Update display boards
self.parking_lot.update_all_display_boards()
# Generate receipt
receipt = Receipt(ticket, payment, parking_fee, charging_fee)
print("โ
Exit processed successfully")
print(receipt.generate_receipt())
return receipt
# ============== Parking Lot (Singleton) ==============
class ParkingLot:
_instance = None
_lock = threading.Lock()
def __new__(cls, name: str = None, address: Address = None):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, name: str = None, address: Address = None):
if self._initialized:
return
self.name = name
self.address = address
self.floors: List[ParkingFloor] = []
self.entrance_gates: List[EntranceGate] = []
self.exit_gates: List[ExitGate] = []
self.pricing_strategy: PricingStrategy = HourlyPricing()
self.reservation_manager = ReservationManager()
self.active_tickets: Dict[str, Ticket] = {}
self._initialized = True
def add_floor(self, floor: ParkingFloor):
self.floors.append(floor)
def add_entrance_gate(self, gate: EntranceGate):
self.entrance_gates.append(gate)
def add_exit_gate(self, gate: ExitGate):
self.exit_gates.append(gate)
def get_available_spot(self, vehicle_type: VehicleType) -> Optional[ParkingSpot]:
for floor in self.floors:
spot = floor.find_available_spot(vehicle_type)
if spot:
return spot
return None
def get_total_capacity(self) -> int:
total = 0
for floor in self.floors:
for spot_type in SpotType:
total += floor.get_available_count(spot_type)
return total
def get_available_count(self, spot_type: SpotType) -> int:
return sum(floor.get_available_count(spot_type) for floor in self.floors)
def update_all_display_boards(self):
for floor in self.floors:
floor.update_display_board()
def display_all_floors(self):
for floor in self.floors:
floor.display_board.display_availability()
def add_active_ticket(self, ticket: Ticket):
self.active_tickets[ticket.ticket_id] = ticket
def remove_active_ticket(self, ticket_id: str):
self.active_tickets.pop(ticket_id, None)
def get_active_ticket(self, ticket_id: str) -> Optional[Ticket]:
return self.active_tickets.get(ticket_id)
def set_pricing_strategy(self, strategy: PricingStrategy):
self.pricing_strategy = strategy
# ============== Admin and Attendant ==============
class Admin:
def __init__(self, admin_id: str, name: str):
self.admin_id = admin_id
self.name = name
def add_floor(self, parking_lot: ParkingLot, floor: ParkingFloor):
parking_lot.add_floor(floor)
print(f"Admin {self.name} added floor {floor.floor_number}")
def add_spot(self, floor: ParkingFloor, spot: ParkingSpot):
floor.add_spot(spot)
print(f"Admin {self.name} added spot {spot.spot_id}")
def update_pricing(self, parking_lot: ParkingLot, strategy: PricingStrategy):
parking_lot.set_pricing_strategy(strategy)
print(f"Admin {self.name} updated pricing strategy")
def mark_spot_maintenance(self, spot: ParkingSpot):
spot.set_state(SpotState.MAINTENANCE)
print(f"Admin {self.name} marked spot {spot.spot_id} for maintenance")
class ParkingAttendant:
def __init__(self, attendant_id: str, name: str):
self.attendant_id = attendant_id
self.name = name
def assist_payment(self, exit_gate: ExitGate, ticket_id: str,
method: PaymentMethod) -> Receipt:
print(f"Attendant {self.name} assisting with payment")
return exit_gate.process_exit(ticket_id, method)
def handle_lost_ticket(self, parking_lot: ParkingLot, vehicle: Vehicle) -> Ticket:
print(f"Attendant {self.name} handling lost ticket for {vehicle.license_plate}")
temp_spot = parking_lot.get_available_spot(vehicle.vehicle_type)
lost_ticket = Ticket(vehicle, temp_spot)
lost_ticket.mark_lost()
return lost_ticket
# ============== Demo ==============
def main():
print("๐
ฟ๏ธ Initializing Parking Lot System...\n")
# Create parking lot
address = Address("123 Main St", "San Francisco", "CA", "94101", "USA")
parking_lot = ParkingLot("Downtown Parking", address)
# Create floors
floor1 = ParkingFloor(1)
floor2 = ParkingFloor(2)
# Add spots to floor 1
for i in range(1, 6):
floor1.add_spot(CompactSpot(f"F1-C{i}", 1))
for i in range(1, 6):
floor1.add_spot(StandardSpot(f"F1-S{i}", 1))
for i in range(1, 4):
floor1.add_spot(MotorcycleSpot(f"F1-M{i}", 1))
for i in range(1, 3):
floor1.add_spot(ElectricSpot(f"F1-E{i}", 1))
# Add spots to floor 2
for i in range(1, 6):
floor2.add_spot(StandardSpot(f"F2-S{i}", 2))
for i in range(1, 4):
floor2.add_spot(LargeSpot(f"F2-L{i}", 2))
# Add floors to parking lot
parking_lot.add_floor(floor1)
parking_lot.add_floor(floor2)
# Create gates
entrance1 = EntranceGate("ENT-1", parking_lot)
exit1 = ExitGate("EXIT-1", parking_lot)
parking_lot.add_entrance_gate(entrance1)
parking_lot.add_exit_gate(exit1)
# Display initial availability
print("๐ Initial Parking Availability:")
parking_lot.display_all_floors()
print()
# Scenario 1: Regular car entry and exit
print("๐ Scenario 1: Car Entry and Exit\n")
car1 = Car("ABC-123")
ticket1 = entrance1.issue_ticket(car1)
time.sleep(2) # Simulate parking time
receipt1 = exit1.process_exit(ticket1.ticket_id, PaymentMethod.CREDIT_CARD)
print()
# Scenario 2: Electric car with charging
print("โก Scenario 2: Electric Car with Charging\n")
electric_car = ElectricCar("TESLA-X", 75)
ticket2 = entrance1.issue_ticket(electric_car)
if isinstance(ticket2.assigned_spot, ElectricSpot):
ticket2.assigned_spot.start_charging(electric_car)
print(f"โก Charging started at {ticket2.assigned_spot.spot_id}")
time.sleep(3) # Simulate charging time
receipt2 = exit1.process_exit(ticket2.ticket_id, PaymentMethod.MOBILE_PAYMENT)
print()
# Scenario 3: Reservation
print("๐
Scenario 3: Advance Reservation\n")
car2 = Car("XYZ-789")
reservation_start = datetime.now()
reservation_end = reservation_start + timedelta(hours=2)
spot_for_reservation = parking_lot.get_available_spot(VehicleType.CAR)
reservation = parking_lot.reservation_manager.create_reservation(
car2, reservation_start, reservation_end, spot_for_reservation
)
print(f"โ
Reservation created: {reservation.reservation_id}")
print(f" Spot: {spot_for_reservation.spot_id}")
print(f" Valid until: {reservation_end + timedelta(minutes=15)}")
print()
# Use reservation
ticket3 = entrance1.issue_ticket(car2, reservation.reservation_id)
print("โ
Reservation activated, vehicle parked\n")
# Display final availability
print("๐ Final Parking Availability:")
parking_lot.display_all_floors()
print("\nโ
Parking Lot System Demo Complete!")
if __name__ == "__main__":
main()
Key Design Decisions
-
Strategy Pattern for Pricing: Allows flexible pricing models (hourly, peak-hour, monthly passes) without modifying core logic. New pricing strategies can be added by implementing the
PricingStrategyinterface. -
Observer Pattern for Display Boards: Display boards automatically update when spot availability changes, ensuring real-time accuracy across all floors.
-
State Pattern for Parking Spots: Spots transition through states (Available โ Reserved โ Occupied โ Available) with proper state validation to prevent invalid operations.
-
Singleton Pattern for ParkingLot: Ensures centralized management of all parking operations with thread-safe instance creation.
-
Factory Pattern Consideration: While not explicitly implemented, the spot creation could be enhanced with a
ParkingSpotFactoryfor more complex spot configurations. -
Thread Safety: Critical sections use locks/synchronized methods to handle concurrent entry/exit operations safely.
-
Money Value Object: Immutable
Moneyclass prevents currency mismatch errors and precision loss in financial calculations. -
Reservation Expiry Monitoring: Background thread automatically cancels expired reservations, maintaining system consistency without manual intervention.