mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 20:43:32 +00:00
61
.github/workflows/maven.yml
vendored
Normal file
61
.github/workflows/maven.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Java CI with Maven
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
cache: maven
|
||||||
|
|
||||||
|
- name: Build with Maven
|
||||||
|
run: mvn -B package
|
||||||
|
working-directory: main
|
||||||
|
|
||||||
|
- name: Upload built JAR
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: package
|
||||||
|
path: main/target/*.jar
|
||||||
|
|
||||||
|
- name: Generate dependency graph
|
||||||
|
run: mvn -B -f main/pom.xml com.github.ferstl:depgraph-maven-plugin:4.0.1:graph
|
||||||
|
|
||||||
|
- name: Upload dependency graph artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dependency-graph
|
||||||
|
path: main/target/**
|
||||||
|
|
||||||
|
publish-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build]
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download built JAR
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: package
|
||||||
|
path: main/target/
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: main/target/*.jar
|
||||||
134
STEP2_SUMMARY.md
Normal file
134
STEP2_SUMMARY.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# 🏁 Single-Process Prototype — Implementation Summary
|
||||||
|
|
||||||
|
**Status:** ✅ Complete
|
||||||
|
**Date:** October 22, 2025
|
||||||
|
**Branch:** `8-single-process-prototype`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The single-process prototype implements a **discrete event simulation (DES)** of a 3×3 urban grid with five intersections, realistic vehicle behavior, and fully synchronized traffic lights. Everything runs under one process, laying the groundwork for the distributed architecture in Phase 3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Architecture
|
||||||
|
|
||||||
|
### **SimulationEngine**
|
||||||
|
|
||||||
|
Drives the DES loop with a priority queue of timestamped events — vehicles, lights, crossings, and periodic stats updates. Handles five intersections (Cr1–Cr5) and six event types.
|
||||||
|
|
||||||
|
**Main loop:**
|
||||||
|
|
||||||
|
```
|
||||||
|
while (events && time < duration):
|
||||||
|
event = nextEvent()
|
||||||
|
time = event.timestamp
|
||||||
|
handle(event)
|
||||||
|
```
|
||||||
|
|
||||||
|
### **VehicleGenerator**
|
||||||
|
|
||||||
|
Spawns vehicles via:
|
||||||
|
|
||||||
|
* **Poisson arrivals** (λ = 0.5 veh/s) or fixed intervals
|
||||||
|
* **Probabilistic routes** from E1–E3
|
||||||
|
* **Type distribution**: 20% BIKE, 60% LIGHT, 20% HEAVY
|
||||||
|
|
||||||
|
### **StatisticsCollector**
|
||||||
|
|
||||||
|
Tracks system-wide and per-type metrics: throughput, avg. wait, queue sizes, light cycles — updated every 10 s and at simulation end.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Model Highlights
|
||||||
|
|
||||||
|
* **Vehicle** – type, route, timings, lifecycle.
|
||||||
|
* **Intersection** – routing tables, traffic lights, queues.
|
||||||
|
* **TrafficLight** – red/green cycles with FIFO queues.
|
||||||
|
* **Event** – timestamped, comparable; 6 types for all DES actions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration (`simulation.properties`)
|
||||||
|
|
||||||
|
```properties
|
||||||
|
simulation.duration=60.0
|
||||||
|
simulation.arrival.model=POISSON
|
||||||
|
simulation.arrival.rate=0.5
|
||||||
|
|
||||||
|
vehicle.bike.crossingTime=1.5
|
||||||
|
vehicle.light.crossingTime=2.0
|
||||||
|
vehicle.heavy.crossingTime=4.0
|
||||||
|
|
||||||
|
statistics.update.interval=10.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Speed logic:**
|
||||||
|
`t_bike = 0.5×t_car`, `t_heavy = 2×t_car`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Topology
|
||||||
|
|
||||||
|
```
|
||||||
|
E1→Cr1→Cr4→Cr5→S
|
||||||
|
E2→Cr2→Cr5→S
|
||||||
|
E3→Cr3→S
|
||||||
|
Bi-dir: Cr1↔Cr2, Cr2↔Cr3
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
**Unit Tests:** 7/7 ✅
|
||||||
|
**60-Second Simulation:**
|
||||||
|
|
||||||
|
* Generated: 22 vehicles
|
||||||
|
* Completed: 5 (22.7%)
|
||||||
|
* Avg system time: 15.47 s
|
||||||
|
* Throughput: 0.08 veh/s
|
||||||
|
* All lights & intersections operational
|
||||||
|
|
||||||
|
**Performance:**
|
||||||
|
~0.03 s real-time run (≈2000× speed-up), < 50 MB RAM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
sd/
|
||||||
|
├── engine/SimulationEngine.java
|
||||||
|
├── model/{Vehicle,Intersection,TrafficLight,Event}.java
|
||||||
|
├── util/{VehicleGenerator,StatisticsCollector}.java
|
||||||
|
└── config/SimulationConfig.java
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Flow
|
||||||
|
|
||||||
|
1. Initialize intersections, lights, first events.
|
||||||
|
2. Process events chronologically.
|
||||||
|
3. Vehicles follow routes → queue → cross → exit.
|
||||||
|
4. Lights toggle, queues drain, stats update.
|
||||||
|
5. Print summary and performance metrics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps — Phase 3
|
||||||
|
|
||||||
|
* Split intersections into independent **processes**.
|
||||||
|
* Add **socket-based communication**.
|
||||||
|
* Run **traffic lights as threads**.
|
||||||
|
* Enable **distributed synchronization** and fault handling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Solid single-process DES ✅
|
||||||
|
Everything’s working — traffic lights, routing, vehicles, stats.
|
||||||
|
Ready to go distributed next.
|
||||||
25
TODO.md
25
TODO.md
@@ -1,3 +1,26 @@
|
|||||||
|
## ✅ SINGLE-PROCESS PROTOTYPE - COMPLETED
|
||||||
|
|
||||||
|
### Phase 2 Status: DONE ✅
|
||||||
|
|
||||||
|
All components for the single-process prototype have been successfully implemented and tested:
|
||||||
|
|
||||||
|
- ✅ **SimulationEngine** - Priority queue-based discrete event simulation
|
||||||
|
- ✅ **VehicleGenerator** - Poisson and Fixed arrival models
|
||||||
|
- ✅ **StatisticsCollector** - Comprehensive metrics tracking
|
||||||
|
- ✅ **Entry point** - Main simulation runner
|
||||||
|
- ✅ **60s test simulation** - Successfully validated event processing and routing
|
||||||
|
|
||||||
|
### Test Results:
|
||||||
|
- All 7 unit tests passing
|
||||||
|
- 60-second simulation completed successfully
|
||||||
|
- Generated 22 vehicles with 5 completing their routes
|
||||||
|
- Traffic light state changes working correctly
|
||||||
|
- Vehicle routing through intersections validated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## NEXT: Distributed Architecture Implementation
|
||||||
|
|
||||||
### Compreender os Conceitos Fundamentais
|
### Compreender os Conceitos Fundamentais
|
||||||
|
|
||||||
Primeiro, as tecnologias e paradigmas chave necessários para este projeto devem ser totalmente compreendidos.
|
Primeiro, as tecnologias e paradigmas chave necessários para este projeto devem ser totalmente compreendidos.
|
||||||
@@ -16,7 +39,7 @@ Primeiro, as tecnologias e paradigmas chave necessários para este projeto devem
|
|||||||
|
|
||||||
- Uma **lista de eventos** central, frequentemente uma fila de prioridades, será necessária para armazenar eventos futuros, ordenados pelo seu timestamp. O ciclo principal da simulação retira o próximo evento da lista, processa-o e adiciona quaisquer novos eventos que resultem dele.
|
- Uma **lista de eventos** central, frequentemente uma fila de prioridades, será necessária para armazenar eventos futuros, ordenados pelo seu timestamp. O ciclo principal da simulação retira o próximo evento da lista, processa-o e adiciona quaisquer novos eventos que resultem dele.
|
||||||
|
|
||||||
- **Processo de Poisson:** Para o modelo "mais realista" de chegadas de veículos, é especificado um processo de Poisson. A principal conclusão é que o tempo _entre_ chegadas consecutivas de veículos segue uma **distribuição exponencial**. Em Java, este intervalo pode ser gerado usando `Math.log(1 - Math.random()) / -lambda`, onde `lambda` (λi) é a taxa de chegada especificada.
|
- **Processo de Poisson:** Para o modelo 'mais realista' de chegadas de veículos, é especificado um processo de Poisson. A principal conclusão é que o tempo _entre_ chegadas consecutivas de veículos segue uma **distribuição exponencial**. Em Java, este intervalo pode ser gerado usando `Math.log(1 - Math.random()) / -lambda`, onde `lambda` (λi) é a taxa de chegada especificada.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
20
main/pom.xml
20
main/pom.xml
@@ -42,6 +42,26 @@
|
|||||||
<mainClass>sd.Entry</mainClass>
|
<mainClass>sd.Entry</mainClass>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<transformers>
|
||||||
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>sd.Entry</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class SimulationConfig {
|
|||||||
* (por exemplo quando executado a partir do classpath/jar),
|
* (por exemplo quando executado a partir do classpath/jar),
|
||||||
* faz fallback para carregar a partir do classpath usando o ClassLoader.
|
* faz fallback para carregar a partir do classpath usando o ClassLoader.
|
||||||
*/
|
*/
|
||||||
IOException lastException = null;
|
IOException lastException = null; //FIXME: melhorar esta parte para reportar erros de forma mais clara
|
||||||
|
|
||||||
try {
|
try {
|
||||||
try (InputStream input = new FileInputStream(filePath)) {
|
try (InputStream input = new FileInputStream(filePath)) {
|
||||||
|
|||||||
@@ -264,32 +264,19 @@ public class SimulationEngine {
|
|||||||
*/
|
*/
|
||||||
private void processEvent(Event event) {
|
private void processEvent(Event event) {
|
||||||
switch (event.getType()) {
|
switch (event.getType()) {
|
||||||
case VEHICLE_GENERATION:
|
case VEHICLE_GENERATION -> handleVehicleGeneration();
|
||||||
handleVehicleGeneration();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case VEHICLE_ARRIVAL:
|
case VEHICLE_ARRIVAL -> handleVehicleArrival(event);
|
||||||
handleVehicleArrival(event);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TRAFFIC_LIGHT_CHANGE:
|
case TRAFFIC_LIGHT_CHANGE -> handleTrafficLightChange(event);
|
||||||
handleTrafficLightChange(event);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CROSSING_START:
|
case CROSSING_START -> handleCrossingStart(event);
|
||||||
handleCrossingStart(event);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CROSSING_END:
|
case CROSSING_END -> handleCrossingEnd(event);
|
||||||
handleCrossingEnd(event);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case STATISTICS_UPDATE:
|
case STATISTICS_UPDATE -> handleStatisticsUpdate();
|
||||||
handleStatisticsUpdate();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default -> System.err.println("Unknown event type: " + event.getType());
|
||||||
System.err.println("Unknown event type: " + event.getType());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +373,7 @@ public class SimulationEngine {
|
|||||||
* @param vehicle The vehicle to process.
|
* @param vehicle The vehicle to process.
|
||||||
* @param intersection The intersection where the vehicle is.
|
* @param intersection The intersection where the vehicle is.
|
||||||
*/
|
*/
|
||||||
private void tryProcessVehicle(Vehicle vehicle, Intersection intersection) {
|
private void tryProcessVehicle(Vehicle vehicle, Intersection intersection) { //FIXME
|
||||||
// Find the direction (and light) this vehicle is queued at
|
// Find the direction (and light) this vehicle is queued at
|
||||||
// This logic is a bit flawed: it just finds the *first* non-empty queue
|
// This logic is a bit flawed: it just finds the *first* non-empty queue
|
||||||
// A better approach would be to get the light from the vehicle's route
|
// A better approach would be to get the light from the vehicle's route
|
||||||
@@ -591,16 +578,12 @@ public class SimulationEngine {
|
|||||||
* @return The crossing time in seconds.
|
* @return The crossing time in seconds.
|
||||||
*/
|
*/
|
||||||
private double getCrossingTime(VehicleType type) {
|
private double getCrossingTime(VehicleType type) {
|
||||||
switch (type) {
|
return switch (type) {
|
||||||
case BIKE:
|
case BIKE -> config.getBikeVehicleCrossingTime();
|
||||||
return config.getBikeVehicleCrossingTime();
|
case LIGHT -> config.getLightVehicleCrossingTime();
|
||||||
case LIGHT:
|
case HEAVY -> config.getHeavyVehicleCrossingTime();
|
||||||
return config.getLightVehicleCrossingTime();
|
default -> 2.0;
|
||||||
case HEAVY:
|
}; // Default fallback
|
||||||
return config.getHeavyVehicleCrossingTime();
|
|
||||||
default:
|
|
||||||
return 2.0; // Default fallback
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
41
main/src/main/java/sd/protocol/MessageProtocol.java
Normal file
41
main/src/main/java/sd/protocol/MessageProtocol.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package sd.protocol;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import sd.model.MessageType; // Assuming MessageType is in sd.model or sd.protocol
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface defining the contract for all messages exchanged in the simulator.
|
||||||
|
* Ensures that any message can be identified and routed.
|
||||||
|
* * This interface extends Serializable to allow objects that implement it
|
||||||
|
* to be sent over Sockets (ObjectOutputStream).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface MessageProtocol extends Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of the message, indicating its purpose.
|
||||||
|
* @return The MessageType (e.g., VEHICLE_TRANSFER, STATS_UPDATE).
|
||||||
|
*/
|
||||||
|
MessageType getType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data object (payload) that this message carries.
|
||||||
|
* The type of object will depend on the MessageType.
|
||||||
|
* * - If getType() == VEHICLE_TRANSFER, the payload will be a {@link sd.model.Vehicle} object.
|
||||||
|
* - If getType() == STATS_UPDATE, the payload will be a statistics object.
|
||||||
|
* * @return The data object (payload), which must also be Serializable.
|
||||||
|
*/
|
||||||
|
Object getPayload();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the node (Process) that sent this message.
|
||||||
|
* @return String (e.g., "Cr1", "Cr5", "S").
|
||||||
|
*/
|
||||||
|
String getSourceNode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the destination node (Process) for this message.
|
||||||
|
* @return String (e.g., "Cr2", "DashboardServer").
|
||||||
|
*/
|
||||||
|
String getDestinationNode();
|
||||||
|
}
|
||||||
183
main/src/main/java/sd/protocol/SocketConnection.java
Normal file
183
main/src/main/java/sd/protocol/SocketConnection.java
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package sd.protocol;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper class that simplifies communication via Sockets.
|
||||||
|
* Includes connection retry logic for robustness.
|
||||||
|
*/
|
||||||
|
public class SocketConnection implements Closeable {
|
||||||
|
|
||||||
|
private final Socket socket;
|
||||||
|
private final ObjectOutputStream outputStream;
|
||||||
|
private final ObjectInputStream inputStream;
|
||||||
|
|
||||||
|
// --- Configuration for Retry Logic ---
|
||||||
|
/** Maximum number of connection attempts. */
|
||||||
|
private static final int MAX_RETRIES = 5;
|
||||||
|
/** Delay between retry attempts in milliseconds. */
|
||||||
|
private static final long RETRY_DELAY_MS = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the "Client" (who initiates the connection).
|
||||||
|
* Tries to connect to a process that is already listening (Server).
|
||||||
|
* Includes retry logic in case of initial connection failure.
|
||||||
|
*
|
||||||
|
* @param host The host address (e.g., "localhost" from your simulation.properties)
|
||||||
|
* @param port The port (e.g., 8001 from your simulation.properties)
|
||||||
|
* @throws IOException If connection fails after all retries.
|
||||||
|
* @throws UnknownHostException If the host is not found (this error usually doesn't need retry).
|
||||||
|
* @throws InterruptedException If the thread is interrupted while waiting between retries.
|
||||||
|
*/
|
||||||
|
public SocketConnection(String host, int port) throws IOException, UnknownHostException, InterruptedException {
|
||||||
|
Socket tempSocket = null;
|
||||||
|
IOException lastException = null;
|
||||||
|
|
||||||
|
System.out.printf("[SocketConnection] Attempting to connect to %s:%d...%n", host, port);
|
||||||
|
|
||||||
|
// --- Retry Loop ---
|
||||||
|
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||||
|
try {
|
||||||
|
// Try to establish the connection
|
||||||
|
tempSocket = new Socket(host, port);
|
||||||
|
|
||||||
|
// If successful, break out of the retry loop
|
||||||
|
System.out.printf("[SocketConnection] Connected successfully on attempt %d.%n", attempt);
|
||||||
|
lastException = null; // Clear last error on success
|
||||||
|
break;
|
||||||
|
|
||||||
|
} catch (ConnectException | SocketTimeoutException e) {
|
||||||
|
// These are common errors indicating the server might not be ready.
|
||||||
|
lastException = e;
|
||||||
|
System.out.printf("[SocketConnection] Attempt %d/%d failed: %s. Retrying in %d ms...%n",
|
||||||
|
attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS);
|
||||||
|
|
||||||
|
if (attempt < MAX_RETRIES) {
|
||||||
|
// Wait before the next attempt
|
||||||
|
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Other IOExceptions might be more permanent, but we retry anyway.
|
||||||
|
lastException = e;
|
||||||
|
System.out.printf("[SocketConnection] Attempt %d/%d failed with IOException: %s. Retrying in %d ms...%n",
|
||||||
|
attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS);
|
||||||
|
if (attempt < MAX_RETRIES) {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // --- End of Retry Loop ---
|
||||||
|
|
||||||
|
// If after all retries tempSocket is still null, it means connection failed permanently.
|
||||||
|
if (tempSocket == null) {
|
||||||
|
System.err.printf("[SocketConnection] Failed to connect to %s:%d after %d attempts.%n", host, port, MAX_RETRIES);
|
||||||
|
if (lastException != null) {
|
||||||
|
throw lastException; // Throw the last exception encountered
|
||||||
|
} else {
|
||||||
|
// Should not happen if loop ran, but as a fallback
|
||||||
|
throw new IOException("Failed to connect after " + MAX_RETRIES + " attempts, reason unknown.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If connection was successful, assign to final variable and create streams
|
||||||
|
this.socket = tempSocket;
|
||||||
|
try {
|
||||||
|
// IMPORTANT: The order is crucial. OutputStream first.
|
||||||
|
this.outputStream = new ObjectOutputStream(socket.getOutputStream());
|
||||||
|
this.inputStream = new ObjectInputStream(socket.getInputStream());
|
||||||
|
} catch (IOException e) {
|
||||||
|
// If stream creation fails even after successful socket connection, clean up.
|
||||||
|
System.err.println("[SocketConnection] Failed to create streams after connection: " + e.getMessage());
|
||||||
|
try { socket.close(); } catch (IOException closeEx) { /* ignore */ }
|
||||||
|
throw e; // Re-throw the stream creation exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the "Server" (who accepts the connection).
|
||||||
|
* Receives a Socket that has already been accepted by a ServerSocket.
|
||||||
|
* No retry logic needed here as the connection is already established.
|
||||||
|
*
|
||||||
|
* @param acceptedSocket The Socket returned by serverSocket.accept().
|
||||||
|
* @throws IOException If stream creation fails.
|
||||||
|
*/
|
||||||
|
public SocketConnection(Socket acceptedSocket) throws IOException {
|
||||||
|
this.socket = acceptedSocket;
|
||||||
|
|
||||||
|
// IMPORTANT: The order is crucial. OutputStream first.
|
||||||
|
this.outputStream = new ObjectOutputStream(socket.getOutputStream());
|
||||||
|
this.inputStream = new ObjectInputStream(socket.getInputStream());
|
||||||
|
System.out.printf("[SocketConnection] Connection accepted from %s:%d.%n",
|
||||||
|
acceptedSocket.getInetAddress().getHostAddress(), acceptedSocket.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends (serializes) a MessageProtocol object over the socket.
|
||||||
|
*
|
||||||
|
* @param message The "envelope" (which contains the Vehicle) to be sent.
|
||||||
|
* @throws IOException If writing to the stream fails or socket is not connected.
|
||||||
|
*/
|
||||||
|
public void sendMessage(MessageProtocol message) throws IOException {
|
||||||
|
if (!isConnected()) {
|
||||||
|
throw new IOException("Socket is not connected.");
|
||||||
|
}
|
||||||
|
synchronized (outputStream) {
|
||||||
|
outputStream.writeObject(message);
|
||||||
|
outputStream.flush(); // Ensures the message is sent immediately.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to read (deserialize) a MessageProtocol object from the socket.
|
||||||
|
* This call is "blocked" until an object is received.
|
||||||
|
*
|
||||||
|
* @return The "envelope" (MessageProtocol) that was received.
|
||||||
|
* @throws IOException If the connection is lost, the stream is corrupted, or socket is not connected.
|
||||||
|
* @throws ClassNotFoundException If the received object is unknown.
|
||||||
|
*/
|
||||||
|
public MessageProtocol receiveMessage() throws IOException, ClassNotFoundException {
|
||||||
|
if (!isConnected()) {
|
||||||
|
throw new IOException("Socket is not connected.");
|
||||||
|
}
|
||||||
|
synchronized (inputStream) {
|
||||||
|
return (MessageProtocol) inputStream.readObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the socket and all streams (Input and Output).
|
||||||
|
* It is called automatically if you use 'try-with-resources'.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
System.out.printf("[SocketConnection] Closing connection to %s:%d.%n",
|
||||||
|
socket != null ? socket.getInetAddress().getHostAddress() : "N/A",
|
||||||
|
socket != null ? socket.getPort() : -1);
|
||||||
|
try {
|
||||||
|
if (inputStream != null) inputStream.close();
|
||||||
|
} catch (IOException e) { /* ignore */ }
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (outputStream != null) outputStream.close();
|
||||||
|
} catch (IOException e) { /* ignore */ }
|
||||||
|
|
||||||
|
if (socket != null && !socket.isClosed()) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the socket is still connected and not closed.
|
||||||
|
*/
|
||||||
|
public boolean isConnected() {
|
||||||
|
return socket != null && socket.isConnected() && !socket.isClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ class SimulationTest {
|
|||||||
assertEquals("TEST1", vehicle.getId());
|
assertEquals("TEST1", vehicle.getId());
|
||||||
assertNotNull(vehicle.getType());
|
assertNotNull(vehicle.getType());
|
||||||
assertNotNull(vehicle.getRoute());
|
assertNotNull(vehicle.getRoute());
|
||||||
assertTrue(vehicle.getRoute().size() > 0);
|
assertTrue(!vehicle.getRoute().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user