ServerLinate.java
/* 
 * Andrea Caravano (www.andreacaravano.net) 
 * 
 * Esercizio 4: "Linate" 
 * Descrizione: Il recente processo di ristrutturazione dell’aeroporto di Milano Linate, ha causato un 
 * insolito aumento dei passeggeri nell’aeroporto, causando un incremento del numero di hostess e 
 * manager del check-in che, parallelamente, si occupano di modificare i dettagli di un volo in partenza. 
 * Nel problema proposto, le hostess sono rappresentate due Thread che operano solo quando non stanno operando i 
 * due altri Thread assegnati ai manager del check-in. 
 * Si sottolinea, tuttavia, che le hostess e i manager del check-in, possono operare contemporaneamente 
 * tra di loro, gestendo la modifica dei parametri del volo mediante apposite strutture di gestione della mutua esclusione. 
 * I dettagli del volo su cui operano i due gruppi di Thread sono i seguenti: 
 * 1)   Località di partenza: Milano Linate 
 * 2)   Località di arrivo: Roma Fiumicino 
 * 3)   Data e orario del volo: 18/12/2019 – 09:50 
 * 4)   Incremento di un unità del numero di passeggeri (numero iniziale = 0) 
 * Il server inizia la propria attività solo quando tutti e 4 i client sono connessi. 
 * Attraverso un menù di scelta (che includa anche la possibilità di chiusura della connessione), il client decide 
 * quale operazione attuare e lo comunica al server, che si occuperà di fornire opportuna risposta di conferma. 
 * Si suppone non vi siano vincoli di precedenza. Il primo gruppo di processi che viene avviato, da avvio alle 
 * operazioni di modifica. 
 * 
 * Possibile soluzione 
 * Componente Server 
 * 
 * N.B.: L'esercizio scaturisce dalla sola fantasia dell'autore e intende rappresentare una applicazione didattica. 
 * I dettagli in esso contenuti potrebbero non essere corrispondenti alla realtà e intendono valutare le abilità nella gestione delle strutture dati proposte. 
 */

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Volo {
    public String localitaPartenza, localitaArrivo, dataVolo;
    public int numeroPasseggeri;

    Volo(String localitaPartenza, String localitaArrivo, String dataVolo, int numeroPasseggeri) {
        this.localitaPartenza = localitaPartenza;
        this.localitaArrivo = localitaArrivo;
        this.dataVolo = dataVolo;
        this.numeroPasseggeri = numeroPasseggeri;
    }

    public void stampaInfoVolo() {
        System.out.println("Informazioni sul volo:");
        System.out.format("\tLocalità di partenza: %s%n", localitaPartenza);
        System.out.format("\tLocalità di arrivo: %s%n", localitaArrivo);
        System.out.format("\tData del volo: %s%n", dataVolo);
        System.out.format("\tNumero di passeggeri: %s%n", numeroPasseggeri);
    }
}

public class ServerLinate {
    static final int PORTALISTEN = 9000;
    static ExecutorService esecutore = Executors.newCachedThreadPool();
    static final int TEMPOCONTROLLO = 5000;
    static final int MINCLIENT = 4;
    static boolean aspettaClient = true;
    static int processiAvviati = 0;
    static CountDownLatch cdlAvvio = new CountDownLatch(1);
    static Lock mutexManager = new ReentrantLock();
    static Lock mutexHostess = new ReentrantLock();
    static Semaphore mutex = new Semaphore(1);
    static Lock mutexVariazioni = new ReentrantLock();
    static int processiDentroGruppoManager = 0;
    static int processiDentroGruppoHostess = 0;
    static Volo volo = new Volo("Milano Linate", "Roma Fiumicino", "18/12/2019 - 09:50", 0);

    public static void main(String[] args) {
        try (ServerSocket procServer = new ServerSocket(PORTALISTEN)) {
            procServer.setSoTimeout(5000);
            System.out.format("Processo server avviato con il seguente indirizzo di socket: %s%n", procServer.getLocalSocketAddress());
            while (aspettaClient) {
                try {
                    Socket tempSocket = procServer.accept();
                    processiAvviati++;
                    if (processiAvviati <= MINCLIENT / 2) {
                        esecutore.execute(() -> {
                            try (Socket varClient = tempSocket) {
                                System.out.format("Thread ID = %d - Indirizzo di socket del client: %s%n", Thread.currentThread().getId(), varClient.getRemoteSocketAddress());
                                manager(varClient);
                            } catch (IOException e) {
                                System.err.format("Errore di avvio della comunicazione: %s%n", e.getMessage());
                            }
                        });
                    } else {
                        esecutore.execute(() -> {
                            try (Socket varClient = tempSocket) {
                                System.out.format("Thread ID = %d - Indirizzo di socket del client: %s%n", Thread.currentThread().getId(), varClient.getRemoteSocketAddress());
                                hostess(varClient);
                            } catch (IOException e) {
                                System.err.format("Errore di avvio della comunicazione: %s%n", e.getMessage());
                            }
                        });
                    }
                } catch (SocketTimeoutException e) {
                    if (processiAvviati >= MINCLIENT) {
                        aspettaClient = false;
                        cdlAvvio.countDown();
                    } else {
                        System.err.println("Ancora non è stato raggiunto il numero di client minimo.");
                    }
                } catch (IOException e) {
                    System.err.format("Errore nella creazione di nuovi socket: %s%n", e.getMessage());
                }
            }
        } catch (IOException e) {
            System.err.format("Errore lato server: %s%n", e.getMessage());
        }
    }

    private static void manager(Socket varClient) {
        try (
                BufferedReader BR = new BufferedReader(new InputStreamReader(varClient.getInputStream(), "UTF-8"));
                PrintWriter PW = new PrintWriter(new OutputStreamWriter(varClient.getOutputStream(), "UTF-8"), true)
        ) {
            cdlAvvio.await();
            mutexManager.lock();
            try {
                processiDentroGruppoManager++;
                if (processiDentroGruppoManager == 1) {
                    mutex.acquire();
                }
                System.out.format("Il manager n. %d ha avviato la propria attività%n", processiDentroGruppoManager);
            } finally {
                mutexManager.unlock();
            }
            PW.println("100"); // Tratto da HTTP: "Continue"

            comunica(BR, PW);

            mutexManager.lock();
            try {
                processiDentroGruppoManager--;
                if (processiDentroGruppoManager == 0) {
                    mutex.release();
                }
            } finally {
                mutexManager.unlock();
            }
        } catch (IOException e) {
            System.err.format("Errore di I/O: %s%n", e.getMessage());
        } catch (InterruptedException e) {
            System.err.format("Errore di gestione dei meccanismi della programmazione concorrente: %s%n", e.getMessage());
        }
    }

    private static void hostess(Socket varClient) {
        try (
                BufferedReader BR = new BufferedReader(new InputStreamReader(varClient.getInputStream(), "UTF-8"));
                PrintWriter PW = new PrintWriter(new OutputStreamWriter(varClient.getOutputStream(), "UTF-8"), true)
        ) {
            cdlAvvio.await();
            mutexHostess.lock();
            try {
                processiDentroGruppoHostess++;
                if (processiDentroGruppoHostess == 1) {
                    mutex.acquire();
                }
                System.out.format("La hostess n. %d ha avviato la propria attività%n", processiDentroGruppoHostess);
            } finally {
                mutexHostess.unlock();
            }
            PW.println("100"); // Tratto da HTTP: "Continue"

            comunica(BR, PW);

            mutexHostess.lock();
            try {
                processiDentroGruppoHostess--;
                if (processiDentroGruppoHostess == 0) {
                    mutex.release();
                }
            } finally {
                mutexHostess.unlock();
            }
        } catch (IOException e) {
            System.err.format("Errore di I/O: %s%n", e.getMessage());
        } catch (InterruptedException e) {
            System.err.format("Errore di gestione dei meccanismi della programmazione concorrente: %s%n", e.getMessage());
        }
    }

    private static void comunica(BufferedReader BR, PrintWriter PW) throws IOException {
        int scelta = new Scanner(BR.readLine()).nextInt();
        while (true) {
            if (scelta == 1) {
                PW.println("100");
                mutexVariazioni.lock();
                try {
                    volo.localitaPartenza = BR.readLine();
                    PW.println("200"); // Tratto da HTTP: "OK"
                } finally {
                    mutexVariazioni.unlock();
                }
            } else if (scelta == 2) {
                PW.println("100");
                mutexVariazioni.lock();
                try {
                    volo.localitaArrivo = BR.readLine();
                    PW.println("200");
                } finally {
                    mutexVariazioni.unlock();
                }
            } else if (scelta == 3) {
                PW.println("100");
                mutexVariazioni.lock();
                try {
                    volo.dataVolo = BR.readLine();
                    PW.println("200");
                } finally {
                    mutexVariazioni.unlock();
                }
            } else if (scelta == 4) {
                PW.println("100");
                mutexVariazioni.lock();
                try {
                    volo.numeroPasseggeri++;
                    PW.println("200");
                } finally {
                    mutexVariazioni.unlock();
                }
            } else if (scelta == 5) {
                PW.println("100"); // Tratto da HTTP: "Continue"
                PW.println("202"); // Tratto da HTTP: "Accepted"
                break;
            } else {
                PW.println("405"); // Tratto da HTTP: "Method not allowed"
            }
            volo.stampaInfoVolo();
            PW.println("100");
            scelta = new Scanner(BR.readLine()).nextInt();
        }
    }
}