Package erki.talk.clients.erki

Source Code of erki.talk.clients.erki.Controller

/*
* © Copyright 2008 by Edgar Kalkowski (eMail@edgar-kalkowski.de)
*
* This file is part of Erki's ErkiTalk Client.
*
* Erki's ErkiTalk Client is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

package erki.talk.clients.erki;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

import erki.api.util.CommandLineParser;
import erki.api.util.Localizor;
import erki.api.util.Logger;
import erki.talk.clients.erki.event.ConnectEvent;
import erki.talk.clients.erki.event.Event;
import erki.talk.clients.erki.event.EventObserver;
import erki.talk.clients.erki.event.NickChangeEvent;
import erki.talk.clients.erki.event.OutputTextEvent;
import erki.talk.clients.erki.event.QuitEvent;
import erki.talk.clients.erki.event.TerminateEvent;
import erki.talk.clients.erki.ui.SwingUI;

/**
* This class contains the main where everything starts. It is also the main
* event collector and dispatcher. To be more specific it does not only dispatch
* the incoming events but schedule them in the order in which they arrived.
* <p>
* Other classes may register themselves as {@link EventObserver} for a special
* type of event and will subsequently be notified if such an event occurrs.
* <p>
* This class obeyes the singleton design pattern. The one and only instance is
* created by the main method via a private constructor. Other classes get
* obtain access to this instance via the {@link #getInstance()} method.
*
* @author Edgar Kalkowski
*/
public class Controller {
   
    /** The version of this program. For use where needed. */
    public static final String VERSION = "0.5.0";
   
    private static final File SETTINGS_FILE = new File(System
            .getProperty("user.home")
            + System.getProperty("file.separator")
            + ".erkitalk"
            + System.getProperty("file.separator") + "erkitalkclientrc")
            .getAbsoluteFile();
   
    /** Stores a mapping from Events to the observers of that event. */
    private Map<Class<? extends Event>, Collection<EventObserver>> eventObservers = new TreeMap<Class<? extends Event>, Collection<EventObserver>>(
            new Comparator<Class<? extends Event>>() {
               
                @Override
                public int compare(Class<? extends Event> o1,
                        Class<? extends Event> o2) {
                    return o1.getCanonicalName().compareTo(
                            o2.getCanonicalName());
                }
            });
   
    /** The event queue to which all events that someone dispatches are added. */
    private LinkedList<Event> eventQueue = new LinkedList<Event>();
   
    /** This is the one and only instance of the controller. */
    private static Controller instance;
   
    private static String nick, nick2;
   
    private static Locale locale;
   
    private static String host = null;
   
    private static int port;
   
    /**
     * Adhering the singleton design pattern this constructor is private and
     * only called once from the main method. Initializes a user interface and
     * starts the event scheduler. Other classes can obtain an instance of this
     * class later by calling the {@link #getInstance()} method.
     */
    private Controller() {
        Controller.instance = this;
       
        // Check on QuitEvent if active connection has to be terminated.
        register(QuitEvent.class, new EventObserver<QuitEvent>() {
           
            @Override
            public void inform(QuitEvent event) {
                saveSettings();
               
                if (!Connection.isConnected()) {
                    dispatchEvent(new TerminateEvent());
                }
            };
        });
       
        // Initiate connection on ConnectEvent and store new host and port for
        // later inclusion in the settings file.
        register(ConnectEvent.class, new EventObserver<ConnectEvent>() {
           
            @Override
            public void inform(ConnectEvent event) {
                host = event.getHost();
                port = event.getPort();
                Connection.makeConnection(event.getHost(), event.getPort());
            }
        });
       
        // Start event scheduler first.
        Logger.info(this, "Starting event scheduler.");
        new Thread("EventScheduler") {
           
            private boolean killed = false;
           
            // Wait for a TerminateEvent and exit the dispatcher thread then.
            {
                register(TerminateEvent.class,
                        new EventObserver<TerminateEvent>() {
                           
                            @Override
                            public void inform(TerminateEvent event) {
                                Logger.info(Controller.this, "Killing event "
                                        + "scheduler.");
                                killed = true;
                            }
                        });
            }
           
            /** Schedules all the events that occurr in this program. */
            @Override
            public void run() {
                super.run();
               
                while (!killed) {
                   
                    synchronized (eventQueue) {
                       
                        if (eventQueue.isEmpty()) {
                           
                            try {
                                eventQueue.wait();
                            } catch (InterruptedException e) {
                            }
                           
                        } else {
                            Event event = eventQueue.poll();
                            Logger.info(Controller.this, "Dispatching " + event
                                    + " (" + eventQueue.size() + " event"
                                    + (eventQueue.size() == 1 ? "" : "s")
                                    + " left).");
                           
                            synchronized (eventObservers) {
                               
                                if (eventObservers
                                        .containsKey(event.getClass())
                                        && !eventObservers
                                                .get(event.getClass())
                                                .isEmpty()) {
                                   
                                    /*
                                     * This conversion to array prevents
                                     * ConcurrentModificationExceptions if one
                                     * of the events triggers the deregistration
                                     * of an observer.
                                     */
                                    EventObserver[] observers = eventObservers
                                            .get(event.getClass()).toArray(
                                                    new EventObserver[0]);
                                   
                                    for (EventObserver observer : observers) {
                                        observer.inform(event);
                                    }
                                   
                                } else {
                                    Logger.warning(Controller.this, "No one "
                                            + "observes "
                                            + event.getClass().getSimpleName()
                                            + "s!");
                                    dispatchEvent(new OutputTextEvent(
                                            "No one observes "
                                                    + event.getClass()
                                                            .getSimpleName()
                                                    + "s!", Formatter
                                                    .getWarningFormat()));
                                }
                            }
                        }
                    }
                }
               
                Logger.info(Controller.this, "Event scheduler killed.");
            }
           
        }.start();
       
        // Start user interface.
        new SwingUI();
       
        // Automatically connect to the last server.
        if (host != null) {
            dispatchEvent(new ConnectEvent(host, port));
        }
    }
   
    /**
     * @return the one and only instance of this class (adhering to the
     *         singleton design pattern).
     */
    public static Controller getInstance() {
        return instance;
    }
   
    /** @return The default nickname to use. */
    public static String getNick() {
        return nick;
    }
   
    /**
     * @return The alternate nickname to use if the default one is already in
     *         use.
     */
    public static String getAlternateNick() {
        return nick2;
    }
   
    /**
     * Everyone can register themself as an observer of specific events via this
     * method.
     *
     * @param <EventType>
     *        The type of event one wants to be notified about.
     * @param event
     *        Class object of the event to register for.
     * @param observer
     *        The observer that wants to be notified if an event of this type
     *        occurrs.
     */
    /*
     * The generic type of this method ensures that only EventObservers of a
     * matching type are added to the eventObservers mapping. Sadly one cannot
     * force this by typing the member variable. But nevertheless this ensures
     * that all the warnings in this class can be ignored.
     */
    public <EventType extends Event> void register(Class<EventType> eventType,
            EventObserver<EventType> observer) {
       
        synchronized (eventObservers) {
            Logger.debug(this, "Registered " + observer + ".");
           
            if (eventObservers.containsKey(eventType)) {
                eventObservers.get(eventType).add(observer);
            } else {
                LinkedList<EventObserver> observers = new LinkedList<EventObserver>();
                observers.add(observer);
                eventObservers.put(eventType, observers);
            }
        }
    }
   
    /**
     * This method removes an observer of a specific event that was added via
     * {@link #register(Class, EventObserver)} before. If no matching observer
     * was found (and thus no observer could be removed) a warning is printed to
     * the log.
     *
     * @param <EventType>
     *        The type of event one no longer wants to be notified about.
     * @param eventType
     *        Class object of the event that shall be deregistered. This would
     *        not really be necessary, but it's more convenient and the one who
     *        deregisters does know this value anyway.
     * @param observer
     *        The actual observer instance that was
     *        {@link #register(Class, EventObserver)}ed before.
     */
    public <EventType extends Event> void deregister(
            Class<EventType> eventType, EventObserver<EventType> observer) {
       
        synchronized (eventObservers) {
           
            if (eventObservers.containsKey(eventType)) {
               
                if (!eventObservers.get(eventType).remove(observer)) {
                    Logger.warning(this, "Tried to deregister " + observer
                            + " but that one was not registered!");
                } else {
                    Logger.debug(this, "Deregistered " + observer + ".");
                }
               
            } else {
                Logger.warning(this, "Tried to deregister " + observer
                        + " but that one was not registered!");
            }
        }
    }
   
    /**
     * Everyone can dispatch events via this method. Events are executed in the
     * order they arrived.
     *
     * @param event
     *        The event to schedule.
     */
    public void dispatchEvent(Event event) {
       
        synchronized (eventQueue) {
            Logger.debug(this, "Added " + event + " to the event queue.");
            eventQueue.offer(event);
            Logger.debug(this, "There "
                    + (eventQueue.size() == 1 ? "is now one" : "are now "
                            + eventQueue.size()) + " event"
                    + (eventQueue.size() == 1 ? "" : "s") + " in the queue.");
            eventQueue.notify();
        }
    }
   
    /**
     * Start Erki's ErkiTalk Client.
     *
     * @param arguments
     *        The command line arguments. Which arguments the program will
     *        accept is displayed when starting it with »--help«.
     */
    public static void main(String[] arguments) {
        loadSettings();
       
        if (locale == null) {
            locale = Locale.getDefault();
        }
       
        Localizor.newInstance(locale);
       
        TreeMap<String, String> args = CommandLineParser.parse(arguments);
       
        if (args.containsKey("--help")) {
            printHelp();
            return;
        }
       
        if (args.containsKey("--debug")) {
            Logger.setDebug(true);
            args.remove("--debug");
        }
       
        if (args.containsKey("--nick")) {
            nick = args.get("--nick");
            args.remove("--nick");
        }
       
        if (args.containsKey("--nick2")) {
            nick2 = args.get("--nick2");
            args.remove("--nick2");
        }
       
        if (args.containsKey("--server")) {
            host = args.get("--server");
            args.remove("--server");
        }
       
        if (args.containsKey("--port")) {
           
            try {
                port = Integer.parseInt(args.get("--port"));
            } catch (NumberFormatException e) {
                Logger.warning(Controller.class, "Could not parse the port "
                        + "number specified on the command line!");
            }
           
            args.remove("--port");
        }
       
        if (args.containsKey("--locale")) {
            locale = Localizor.parseLocale(args.get("--locale"));
            args.remove("--locale");
        }
       
        if (!args.keySet().isEmpty()) {
           
            for (String arg : args.keySet()) {
                Logger.warning(Controller.class,
                        "Unknown command line option: " + arg + "!");
            }
        }
       
        // This must be the one and only place the controller ist instanciated!
        new Controller();
    }
   
    private static void printHelp() {
        System.out.println("This is Erki's ErkiTalk Client v" + VERSION);
        System.out.println("Usage: java Controller [OPTIONS]");
        System.out.println("Supported OPTIONS:");
        System.out.println("  --debug          Print lots of debug "
                + "information.");
        System.out.println("  --locale LOCALE  Use the language LOCALE for "
                + "the user interface. LOCALE");
        System.out.println("                   consists of two letter "
                + "language codes like de_DE.");
        System.out.println("  --nick NAME      Use NAME as default nickname.");
        System.out.println("  --nick2 NAME     Use NAME as alternate "
                + "nickname if the default nickname is");
        System.out.println("                   already in use.");
        System.out.println("  --port PORT      Use PORT for the server to "
                + "connect to on startup.");
        System.out.println("  --server HOST    Automatically connect to HOST "
                + "on startup.");
        System.out.println("All these options may also be specified in the "
                + "config file");
        System.out.println(SETTINGS_FILE.toString() + ".");
        System.out.println("However command line arguments override the "
                + "settings from the file.");
        System.out.println("© 2008 by Edgar Kalkowski (eMail@edgar-"
                + "kalkowski.de)");
    }
   
    private static void loadSettings() {
       
        try {
            BufferedReader fileIn = new BufferedReader(new FileReader(
                    SETTINGS_FILE));
            String line, country = Locale.getDefault().getCountry(), language = Locale
                    .getDefault().getLanguage(), variant = Locale.getDefault()
                    .getVariant();
           
            while ((line = fileIn.readLine()) != null) {
               
                if (line.startsWith("nick = ")) {
                    nick = line.substring("nick = ".length());
                }
               
                if (line.startsWith("nick2 = ")) {
                    nick2 = line.substring("nick2 = ".length());
                }
               
                if (line.startsWith("server = ")) {
                    host = line.substring("server = ".length());
                }
               
                if (line.startsWith("port = ")) {
                    port = Integer.parseInt(line.substring("port = ".length()));
                }
               
                if (line.startsWith("country = ")) {
                    country = line.substring("country = ".length());
                }
               
                if (line.startsWith("language = ")) {
                    language = line.substring("language = ".length());
                }
               
                if (line.startsWith("variant = ")) {
                    variant = line.substring("variant = ".length());
                }
            }
           
            locale = new Locale(language, country, variant);
            fileIn.close();
            Logger.info(Controller.class, "Loaded settings from "
                    + SETTINGS_FILE + ".");
        } catch (FileNotFoundException e) {
            Logger.info(Controller.class, "No settings file found at "
                    + SETTINGS_FILE.toString() + ". Using default values.");
        } catch (IOException e) {
            Logger.warning(Controller.class, "Could not read settings file "
                    + SETTINGS_FILE.toString()
                    + ". Falling back to default values.");
        }
    }
   
    private static void saveSettings() {
       
        if (!SETTINGS_FILE.getParentFile().exists()
                && !SETTINGS_FILE.getParentFile().mkdirs()) {
            Logger.warning(Controller.class, "Could not create settings "
                    + "directory (" + SETTINGS_FILE.getParentFile()
                    + ")! So the settings could not be saved.");
            return;
        } else if (SETTINGS_FILE.getParentFile().exists()
                && !SETTINGS_FILE.getParentFile().isDirectory()) {
            Logger.warning(Controller.class, "The directory where the "
                    + "settings file should be stored ("
                    + SETTINGS_FILE.getParentFile() + ") is actually a file "
                    + "itself! So the settings could not be saved.");
            return;
        }
       
        PrintWriter fileOut;
       
        try {
           
            try {
                fileOut = new PrintWriter(new OutputStreamWriter(
                        new FileOutputStream(SETTINGS_FILE), "UTF-8"));
                fileOut.println("nick = " + nick);
                fileOut.println("nick2 = " + nick2);
                fileOut.println("server = " + host);
                fileOut.println("port = " + port);
                fileOut.println("country = " + locale.getCountry());
                fileOut.println("language = " + locale.getLanguage());
                fileOut.println("variant = " + locale.getVariant());
                fileOut.close();
                Logger.info(Controller.class, "Saved settings to "
                        + SETTINGS_FILE + ".");
            } catch (UnsupportedEncodingException e) {
                Logger.warning(Controller.class, "You system seems not to "
                        + "support UTF-8 so unicode characters will be messed "
                        + "up in the settings file!");
                fileOut = new PrintWriter(new FileOutputStream(SETTINGS_FILE));
            }
           
        } catch (FileNotFoundException e) {
            Logger.warning(Controller.class, "Could not open " + SETTINGS_FILE
                    + " so no settings could be saved!");
        }
    }
}
TOP

Related Classes of erki.talk.clients.erki.Controller

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.