ServerTelegiornale.java
/* 
 * Andrea Caravano (www.andreacaravano.net) 
 * 
 * Esercizio 5: "Telegiornale" 
 * Descrizione: La redazione di TGJava24 ha adottato un’infrastruttura di rete che prevede un numero variabile 
 * di assistenti alla regia e redattori che hanno la necessità di comunicare con il giornalista in onda in tempo reale, 
 * per informarlo su eventuali variazioni e/o correzioni alle notizie oggetto del telegiornale. 
 * Si suppone che il primo client che si collega sia il giornalista in onda e tutti i successivi siano assistenti alla 
 * regia e redattori. 
 * Un possibile esempio di variazione alle notizie comunicata dai redattori può essere la seguente: 
 * Traffico intenso sulla A14 
 * A causa delle recenti manifestazioni nell’area di Bologna, un’insolita coda si è formata all’altezza del 
 * chilometro 721 dell’autostrada A14. 
 * La struttura della notizia deve includere il titolo della notizia (su una sola linea) e il testo esteso della notizia, 
 * multilinea. Il client del giornalista dovrà rimanere costantemente in ascolto di eventuali variazioni comunicate 
 * dai redattori. 
 * 
 * Sviluppare la sola 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.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
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;

public class ServerTelegiornale {

    static final int PORTALISTEN = 9000;
    static int contaClient = 0;
    static ExecutorService esecutore = Executors.newCachedThreadPool();
    static Lock mutexLettori = new ReentrantLock();
    static Lock mutexScrittori = new ReentrantLock();
    static Semaphore semLettura = new Semaphore(1);
    static Semaphore sincro = new Semaphore(1);
    static int processiDentroScrittori = 0;
    static int processiDentroLettori = 0;
    static TreeMap<String, List<String>> notizie = new TreeMap<>();

    public static void main(String[] args) {
        System.out.println("TGJava24 - REDAZIONE");
        System.out.println("====================");
        try (ServerSocket procServer = new ServerSocket(PORTALISTEN)) {
            System.out.format("Processo server avviato con il seguente indirizzo di socket: %s%n", procServer.getLocalSocketAddress());
            while (true) {
                try {
                    Socket tempClient = procServer.accept();
                    contaClient++;
                    if (contaClient == 1) {
                        esecutore.execute(() -> {
                            try (Socket client = tempClient) {
                                System.out.format("Thread ID = %d - Indirizzo di socket del client: %s%n", Thread.currentThread().getId(), client.getRemoteSocketAddress());
                                lettore(client);
                            } catch (IOException e) {
                                System.err.format("Errore di avvio della comunicazione: %s%n", e.getMessage());
                            }
                        });
                    } else {
                        esecutore.execute(() -> {
                            try (Socket client = tempClient) {
                                System.out.format("Thread ID = %d - Indirizzo di socket del client: %s%n", Thread.currentThread().getId(), client.getRemoteSocketAddress());
                                scrittore(client);
                            } catch (IOException e) {
                                System.err.format("Errore di avvio della comunicazione: %s%n", e.getMessage());
                            }
                        });
                    }
                } 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 scrittore(Socket client) {
        try (BufferedReader BR = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
             PrintWriter PW = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), "UTF-8"), true)) {
            System.out.println("Thread scrittore avviato.");
            PW.println("RUOLO = Scrittore");
            while (true) {
                String titoloNotiziaLocale = BR.readLine();
                List<String> testoNotiziaLocale = new ArrayList<>();
                String singolaRiga;
                do {
                    singolaRiga = BR.readLine();
                    if (singolaRiga.isEmpty() == false)
                        testoNotiziaLocale.add(singolaRiga);
                } while (singolaRiga.isEmpty() == false);
                System.out.println("Presa in carico una nuova notizia.");
                PW.println("202"); // Tratto da HTTP: "Accepted"
                inizioScrittura();

                notizie.put(titoloNotiziaLocale, testoNotiziaLocale);

                fineScrittura();
            }
        } catch (UnsupportedEncodingException e) {
            System.out.format("Errore: codifica non supportata: %s", e.getMessage());
        } 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 lettore(Socket client) {
        try (BufferedReader BR = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
             PrintWriter PW = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), "UTF-8"), true)) {
            System.out.println("Thread lettore avviato.");
            PW.println("RUOLO = Lettore");
            TreeMap<String, List<String>> notizieInviate = new TreeMap<>();
            while (true) {
                inizioLettura();

                for (Map.Entry<String, List<String>> n : notizie.entrySet()) {
                    if (notizieInviate.containsKey(n.getKey()) == false) {
                        PW.println(n.getKey());
                        List<String> righeNotizia = n.getValue();
                        for (String r : righeNotizia) {
                            PW.println(r);
                        }
                        notizieInviate.put(n.getKey(), n.getValue());
                    }
                }

                fineLettura();
            }
        } catch (UnsupportedEncodingException e) {
            System.out.format("Errore: codifica non supportata: %s", e.getMessage());
        } 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 fineLettura() throws InterruptedException {
        mutexLettori.lock();
        try {
            processiDentroLettori--;
            if (processiDentroLettori == 0) {
                sincro.release();
            }
        } finally {
            mutexLettori.unlock();
        }
    }

    private static void inizioLettura() throws InterruptedException {
        semLettura.acquire();
        mutexLettori.lock();
        try {
            processiDentroLettori++;
            if (processiDentroLettori == 1) {
                sincro.acquire();
            }
        } finally {
            mutexLettori.unlock();
            semLettura.release();
        }
    }

    private static void inizioScrittura() throws InterruptedException {
        mutexScrittori.lock();
        try {
            processiDentroScrittori++;
            if (processiDentroScrittori == 1) {
                semLettura.acquire();
            }
            sincro.acquire();
        } finally {
            mutexScrittori.unlock();
        }
    }

    private static void fineScrittura() throws InterruptedException {
        mutexScrittori.lock();
        try {
            sincro.release();
            processiDentroScrittori--;
            if (processiDentroScrittori == 0) {
                semLettura.release();
            }
        } finally {
            mutexScrittori.unlock();
        }
    }
}