import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.logging.Logger;

/**
 * Cette classe permet d'assurer l'unicité de l'instance de l'application. Deux applications ne peuvent pas être lancées
 * simultanément. Voici un exemple typique d'utilisation :
 
 <pre>
 * // Port à utiliser pour communiquer avec l'instance de l'application lancée.
 * final int PORT = 32145;
 * // Message à envoyer à l'application lancée lorsqu'une autre instance essaye de démarrer.
 * final String MESSAGE = &quot;nomDeMonApplication&quot;;
 * // Actions à effectuer lorsqu'une autre instance essaye de démarrer.
 * final Runnable RUN_ON_RECEIVE = new Runnable() {
 *     public void run() {
 *         // On exécute ce runnable dans l'EDT
 *         SwingUtilities.invokeLater(new Runnable() {
 *             public void run() {
 *                 if(mainFrame != null) {
 *                     // Si la fenêtre n'est pas visible (uniquement dans le systray par exemple), on la rend visible.
 *                     if(!mainFrame.isVisible())
 *                         mainFrame.setVisible(true);
 *                     // On demande à la mettre au premier plan.
 *                     mainFrame.toFront();
 *                 }
 *             }
 *         });
 *     }                   
 * });
 *                 
 * UniqueInstance uniqueInstance = new UniqueInstance(PORT, MESSAGE, RUN_ON_RECEIVE);
 * // Si aucune autre instance n'est lancée...
 * if(uniqueInstance.launch()) {
 *     // On démarre l'application.
 *     new MonApplication();
 * }
 </pre>
 
 @author rom1v
 */
public class UniqueInstance {

    /** Port d'écoute utilisé pour l'unique instance de l'application. */
    private int port;

    /** Message à envoyer à l'éventuelle application déjà lancée. */
    private String message;

    /** Actions à effectuer lorsqu'une autre instance de l'application a indiqué qu'elle avait essayé de démarrer. */
    private Runnable runOnReceive;

    /**
     * Créer un gestionnaire d'instance unique de l'application.
     
     @param port
     *            Port d'écoute utilisé pour l'unique instance de l'application.
     @param message
     *            Message à envoyer à l'éventuelle application déjà lancée, {@code null} si aucune action.
     @param runOnReceive
     *            Actions à effectuer lorsqu'une autre instance de l'application a indiqué qu'elle avait essayé de
     *            démarrer, {@code null} pour aucune action.
     */
    public UniqueInstance(int port, String message, Runnable runOnReceive) {
        assert port > && port < << 16 "Le port doit être entre 1 et 65535";
        assert message != null || runOnReceive == null "Il y a des actions à effectuer => le message ne doit pas être null.";
        this.port = port;
        this.message = message;
        this.runOnReceive = runOnReceive;
    }

    /**
     * Créer un gestionnaire d'instance unique de l'application. Ce constructeur désactive la communication entre
     * l'instance déjà lancée et l'instance qui essaye de démarrer.
     
     @param port
     *            Port d'écoute utilisé pour l'unique instance de l'application.
     */
    public UniqueInstance(int port) {
        this(port, null, null);
    }

    /**
     * Essaye de démarrer le gestionnaire d'instance unique. Si l'initialisation a réussi, c'est que l'instance est
     * unique. Sinon, c'est qu'une autre instance de l'application est déjà lancée. L'appel de cette méthode prévient
     * l'application déjà lancée qu'une autre vient d'essayer de se connecter.
     
     @return {@code true} si l'instance de l'application est unique.
     */
    public boolean launch() {
        /* Indique si l'instance du programme est unique. */
        boolean unique;

        try {
            /* On crée une socket sur le port défini. */
            final ServerSocket server = new ServerSocket(port);

            /* Si la création de la socket réussit, c'est que l'instance du programme est unique, aucune autre n'existe. */
            unique = true;

            /* S'il y a des actions à faire lorsqu'une autre instance essaye de démarrer... */
            if(runOnReceive != null) {

                /* On lance un Thread d'écoute sur ce port. */
                Thread portListenerThread = new Thread("UniqueInstance-PortListenerThread") {

                    {
                        setDaemon(true);
                    }

                    @Override public void run() {
                        /* Tant que l'application est lancée... */
                        while(true) {
                            try {
                                /* On attend qu'une socket se connecte sur le serveur. */
                                final Socket socket = server.accept();

                                /* Si une socket est connectée, on écoute le message envoyé dans un nouveau Thread. */
                                new Thread("UniqueInstance-SocketReceiver") {

                                    {
                                        setDaemon(true);
                                    }

                                    @Override public void run() {
                                        receive(socket);
                                    }
                                }.start();
                            catch(IOException e) {
                                Logger.getLogger("UniqueInstance").warning("Attente de connexion de socket échouée.");
                            }
                        }
                    }
                };

                /* On démarre le Thread. */
                portListenerThread.start();
            }
        catch(IOException e) {
            /* Si la création de la socket échoue, c'est que l'instance n'est pas unique, une autre n'existe. */
            unique = false;

            /* Si des actions sont prévues par l'instance déjà lancée... */
            if(runOnReceive != null) {
                /*
                 * Dans ce cas, on envoie un message à l'autre instance de l'application pour lui demander d'avoir le
                 * focus (par exemple).
                 */
                send();
            }
        }
        return unique;
    }

    /**
     * Envoie un message à l'instance de l'application déjà ouverte.
     */
    private void send() {
        PrintWriter pw = null;
        try {
            /* On se connecte sur la machine locale. */
            Socket socket = new Socket("localhost", port);

            /* On définit un PrintWriter pour écrire sur la sortie de la socket. */
            pw = new PrintWriter(socket.getOutputStream());

            /* On écrit le message sur la socket. */
            pw.write(message);
        catch(IOException e) {
            Logger.getLogger("UniqueInstance").warning("Écriture sur flux de sortie de la socket échouée.");
        finally {
            if(pw != null)
                pw.close();
        }
    }

    /**
     * Reçoit un message d'une socket s'étant connectée au serveur d'écoute. Si ce message est le message de l'instance
     * unique, l'application demande le focus.
     
     @param socket
     *            Socket connectée au serveur d'écoute.
     */
    private void receive(Socket socket) {
        Scanner sc = null;

        try {
            /* On n'écoute que 5 secondes, si aucun message n'est reçu, tant pis... */
            socket.setSoTimeout(5000);

            /* On définit un Scanner pour lire sur l'entrée de la socket. */
            sc = new Scanner(socket.getInputStream());

            /* On ne lit qu'une ligne. */
            String s = sc.nextLine();

            /* Si cette ligne est le message de l'instance unique... */
            if(message.equals(s)) {
                /* On exécute le code demandé. */
                runOnReceive.run();
            }
        catch(IOException e) {
            Logger.getLogger("UniqueInstance").warning("Lecture du flux d'entrée de la socket échoué.");
        finally {
            if(sc != null)
                sc.close();
        }

    }

}