de.nec.nle.siafu.control.Controller.java Source code

Java tutorial

Introduction

Here is the source code for de.nec.nle.siafu.control.Controller.java

Source

/*
 * Copyright NEC Europe Ltd. 2006-2007
 * 
 * This file is part of the context simulator called Siafu.
 * 
 * Siafu 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 2 of the License, or (at your option) any later
 * version.
 * 
 * Siafu 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 de.nec.nle.siafu.control;

import java.io.File;
import java.io.IOException;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.eclipse.swt.widgets.Display;

import de.nec.nle.siafu.control.progress.ConsoleProgress;
import de.nec.nle.siafu.control.progress.GUIProgress;
import de.nec.nle.siafu.control.progress.Progress;
import de.nec.nle.siafu.exceptions.GUINotReadyException;
import de.nec.nle.siafu.externalCommand.CommandListener;
import de.nec.nle.siafu.graphics.GUI;
import de.nec.nle.siafu.graphics.Markers;
import de.nec.nle.siafu.graphics.markers.Marker;
import de.nec.nle.siafu.model.SimulationData;
import de.nec.nle.siafu.model.Trackable;
import de.nec.nle.siafu.model.World;

/**
 * This is the main class of the simulator. Upon running its main method, a
 * new <code>Controller</code> is created. This, in turn starts three
 * threads:
 * <li> Simulation: does the actual simulation and number crunching
 * <li>GUI: runs the user interface, if selected in the configuration file
 * <li>CommandListener: listens for external commands on the TCP port defined
 * in the config file
 * <p>
 * Controller also acts as the reference point between the three threads,
 * allowing them to share objects, such as the <code>World</code> or each
 * other.
 * 
 * @author Miquel Martin
 * 
 */
public class Controller {
    /** Default value for CSV interval. */
    private static final int DEFAULT_CSV_INTERVAL = 300;

    /** Default value for the cache size. */
    private static final int DEFAULT_CACHE_SIZE = 100;

    /** Default value for the UI speed. */
    private static final int DEFAULT_UI_SPEED = 50;

    /** Default value for the TCP listening port. */
    private static final int DEFAULT_PORT = 4444;

    /**
     * Configuration folder
     * 
     *   Windows: C:\Documents and Settings\<user>\Application Data\Siafu (XP)
     *            C:\Users\<user>\AppData\Roaming\Siafu (Vista, 7)
     *   Unix:    /home/<user>/.Siafu
     */
    public static final String CONFIG_PATH = ((System.getProperty("os.name").toLowerCase().startsWith("windows")
            && System.getenv("APPDATA") != null) ?
    // Windows
                    System.getenv("APPDATA") + File.separator + "Siafu" :
                    // Unix
                    System.getProperty("user.home") + File.separator + ".Siafu");

    /** Default value for the gradient path. */
    public static final String DEFAULT_GRADIENT_PATH = CONFIG_PATH + File.separator + "CalculatedGradients"
            + File.separator;

    /** Default config file location. */
    public static final String DEFAULT_CONFIG_FILE = CONFIG_PATH + File.separator + "config.xml";

    /**
     * The configuration file for the simulator.
     */
    private XMLConfiguration config;

    // FIXME refactor ro siafuConfig

    /**
     * The simulation <code>Runnable</code>.
     */
    private Simulation simulation;

    /**
     * The GUI (if enabled in the configuration) for the simulation.
     */
    private GUI gui;

    /** Whether the GUI is being used or not. */
    private boolean guiUsed;

    /**
     * The Runnable that becomes a listening thread for commands on the TCP
     * port.
     */
    private CommandListener commandListener;

    /**
     * The Progress object that displays simulation load status.
     */
    private static Progress progress;

    /**
     * Get the Progress class that displays simulation laod status.
     * 
     * @return the progress instance to use
     */
    public static Progress getProgress() {
        return progress;
    }

    /**
     * Initialize the simulator itself, and run the simulation.
     * 
     * @param configPath the file that defines the parameters of the
     *            simulation
     * @param simulationPath the path to the simulation data
     */
    public Controller(final String configPath, final String simulationPath) {
        String verifiedConfigPath = configPath;

        if (configPath == null) {
            verifiedConfigPath = Controller.DEFAULT_CONFIG_FILE;
        }

        try {
            config = new XMLConfiguration(verifiedConfigPath);
            System.out.println("Using configuration at " + verifiedConfigPath);
        } catch (ConfigurationException e) {
            System.out.println("The config file doesn't exist or " + "is malformed. Recreating.");
            config = createDefaultConfigFile();
        }

        // Command Listener thread (for external commands)
        if (config.getBoolean("commandlistener.enable")) {
            int tcpPort = config.getInt("commandlistener.tcpport");
            try {
                commandListener = new CommandListener(this, tcpPort);
                // Start threads
                new Thread(commandListener, "Command Listener thread").start();
            } catch (IOException e) {
                System.err.println("The TCP port " + tcpPort + " is already in use. Is there another copy of "
                        + "Siafu running? Consider changing the port " + "number in the config file.");
                return;
            }
        }

        guiUsed = config.getBoolean("ui.usegui");

        if (guiUsed) {
            // Printout to the GUI
            progress = new GUIProgress();

            // If there's a GUI, let it load the
            // simulation, if it's avaialble
            gui = new GUI(this, simulationPath);
            Display display = new Display();
            display.syncExec(gui);
        } else if (simulationPath != null) {
            // Printout to the Console
            progress = new ConsoleProgress();

            // Start the simulation without a GUI
            simulation = new Simulation(simulationPath, this);
        } else {
            // No simulation and no GUI to load. This won't
            // work. Die.
            System.err.println(
                    "Please activate the GUI in the config file " + "or provide a simulation at the command line.");
            System.exit(1);
        }

    }

    /**
     * Create a config file with default values. This is used when the config
     * file doesn't exist in the first place.
     * 
     * @return the newly created configuration file.
     */
    private XMLConfiguration createDefaultConfigFile() {
        System.out.println("Creating a default configuration file at " + DEFAULT_CONFIG_FILE);
        XMLConfiguration newConfig = new XMLConfiguration();
        newConfig.setRootElementName("configuration");
        newConfig.setProperty("commandlistener.enable", true);
        newConfig.setProperty("commandlistener.tcpport", DEFAULT_PORT);
        newConfig.setProperty("ui.usegui", true);
        newConfig.setProperty("ui.speed", DEFAULT_UI_SPEED);
        newConfig.setProperty("ui.gradientcache.prefill", true);
        newConfig.setProperty("ui.gradientcache.size", DEFAULT_CACHE_SIZE);
        newConfig.setProperty("output.type", "null");
        newConfig.setProperty("output.csv.path",
                System.getProperty("user.home") + File.separator + "SiafuContext.csv");
        newConfig.setProperty("output.csv.interval", DEFAULT_CSV_INTERVAL);
        newConfig.setProperty("output.csv.keephistory", true);

        try {
            newConfig.setFileName(DEFAULT_CONFIG_FILE);
            newConfig.save();
        } catch (ConfigurationException e) {
            throw new RuntimeException("Can not create a default config file at " + DEFAULT_CONFIG_FILE, e);
        }

        return newConfig;
    }

    /**
     * Request for the simulation to stop. note that this doesn't kill the GUI
     * (if it is being used).
     * 
     */
    public synchronized void stopSimulation() {
        simulation.die();
        notifyAll();
        simulation = null;
    }

    /**
     * Start the simulation pointed to by the simulationpath.
     * 
     * @param simulationPath the path to the folder or jar containing the
     *            simulation.
     */
    public void startSimulation(final String simulationPath) {
        simulation = new Simulation(simulationPath, this);
    }

    /**
     * Get the configuration for the simulator.
     * 
     * @return the <code>Configuration</code> object
     */
    public XMLConfiguration getSiafuConfig() {
        return config;
    }

    /**
     * Notifies the controler that the GUI has finished drawing an iteration
     * and the simulation can continue.
     */
    public synchronized void setDrawingCondluded() {
        notifyAll();
    }

    /**
     * Asks the gui whether it wants to draw this iteration, and pauses until
     * it wants to.
     * <p>
     * If the gui is not enabled in the configuration file, or if it chooses
     * to skip this iteration, the method only action is to notify the GUI
     * that it just missed an iteration. Note that the GUI might choose to
     * skip <code>iterationStep</code> iterations to increase the
     * interface's speed.
     * 
     */
    public synchronized void scheduleDrawing() {
        if ((gui != null) && gui.requestPermissionToDraw()) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Pauses the simulation. See <code>isPaused()</code> for details. If no
     * gui is used, the simulation thread still pauses.
     * 
     * @param state a boolean with the value true will pause the simulation,
     *            while false will resume it.
     */
    public void setPaused(final boolean state) {
        simulation.setPaused(state);
    }

    /**
     * Reports on the pause state of the simulation.
     * <p>
     * When paused, time stops, and all automatic behaviour is interrupted.
     * However, using the GUI, the user can still manipulate agents in his
     * control, or update variables through the external command interface.
     * 
     * @return true if the simulation is paused, false otherwise
     */
    public boolean isPaused() {
        return simulation.isPaused();
    }

    /**
     * Get the world being simulated.
     * 
     * @return a World object representing the world.
     */
    public World getWorld() {
        return simulation.getWorld();
    }

    /**
     * Get the folder or jar file that contains all the simulation data.
     * 
     * @return a File that represents the folder with the images
     */
    public SimulationData getSimulationData() {
        return simulation.getSimulationData();
    }

    /**
     * Get the command listener for this simulator's run.
     * 
     * @return the command listener
     */
    public CommandListener getCommandListener() {
        return commandListener;
    }

    /**
     * End the simulator, that is quit. Stop the simulation, but also the GUI
     * and anything else that's running.
     */
    public synchronized void endSimulator() {
        if (commandListener != null) {
            commandListener.die();
        }

        // End the simulation
        if (simulation != null) {
            simulation.die();
        }

        if (gui != null) {
            gui.die();
        }
    }

    /**
     * Get the simulator's GUI object.
     * 
     * @return a GUI object for the simulator's Graphical User Interface
     */
    public GUI getGUI() {
        return gui;
    }

    /**
     * Find out if the gui is being used.
     * 
     * @return true if the gui is used
     */
    public boolean isGuiUsed() {
        return guiUsed;
    }

    /**
     * Add a Marker to the simulation GUI. If the GUI is not ready, this
     * method performs no action a GUINotReadyException is thrown. If the GUI
     * is not being used this method returns silently and does nothing.
     * 
     * @param m the marker to add
     * @throws GUINotReadyException if the GUI can not draw the mark at the
     *             moment.
     */
    public void addMarker(final Marker m) throws GUINotReadyException {
        if (guiUsed) {
            if (gui.canReceiveCommands()) {
                gui.getMarkers().addMarker(m, Markers.Type.INTERNAL);
            } else {
                throw new GUINotReadyException("Can't add tracks until the simulation has started.");
            }
        }
    }

    /**
     * Remove all Markers from the simulation GUI. If the GUI is not ready,
     * this method performs no action a GUINotReadyException is thrown. If the
     * GUI is not being used this method returns silently and does nothing.
     * 
     * @throws GUINotReadyException if the GUI can not draw the mark at the
     *             moment.
     */
    public void unMarkAll() throws GUINotReadyException {
        if (guiUsed) {
            if (gui.canReceiveCommands()) {
                gui.getMarkers().removeAllMarkers(Markers.Type.INTERNAL);
            } else {
                throw new GUINotReadyException("Can't remove tracks until the simulation " + "has started.");
            }
        }
    }

    /**
     * Remove the Marker for this Trackable from the simulation GUI F. If the
     * GUI is not ready, this method performs no action a GUINotReadyException
     * is thrown. If the GUI is not being used this method returns silently
     * and does nothing.
     * 
     * @param t the Trackable to unmark
     * @throws GUINotReadyException if the GUI can not draw the mark at the
     *             moment.
     */
    public void unMark(final Trackable t) throws GUINotReadyException {
        if (guiUsed) {
            if (gui.canReceiveCommands()) {
                gui.getMarkers().removeMarker(t, Markers.Type.INTERNAL);
            } else {
                throw new GUINotReadyException("Can't remove tracks until the simulation " + "has started.");
            }
        }
    }

    /**
     * Find out if the given Trackable is marked in the GUI. If the GUI is not
     * ready, this method throws a GUINotReadyException. If the GUI is not
     * being used this method returns false.
     * 
     * @param t the Trackable about which we are asking
     * @return true if the trackable has been marked, false otherwise
     * @throws GUINotReadyException if the GUI can not draw the mark at the
     *             moment.
     */
    public boolean isMarked(final Trackable t) throws GUINotReadyException {
        if (guiUsed) {
            if (gui.canReceiveCommands()) {
                return gui.getMarkers().isTracked(t, Markers.Type.INTERNAL);
            } else {
                throw new GUINotReadyException("Can't check tracks until the simulation has started.");
            }
        }
        return false;
    }

    /**
     * Find out if the simulation is finished loading and already running.
     * 
     * @return true if the simulation is running.
     */
    public boolean isSimulationRunning() {
        return simulation != null && simulation.isSimulationRunning();
    }
}