package kernel;
import static rescuecore2.misc.java.JavaTools.instantiate;
import static rescuecore2.misc.java.JavaTools.instantiateFactory;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JFrame;
import javax.swing.JDialog;
import javax.swing.JButton;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Frame;
import rescuecore2.connection.ConnectionException;
import rescuecore2.connection.ConnectionManager;
import rescuecore2.config.Config;
import rescuecore2.config.ConfigException;
import rescuecore2.config.IntegerValueConstraint;
import rescuecore2.config.ClassNameSetValueConstraint;
import rescuecore2.config.ClassNameValueConstraint;
import rescuecore2.components.ComponentLauncher;
import rescuecore2.components.Component;
import rescuecore2.components.ComponentInitialisationException;
import rescuecore2.components.ComponentConnectionException;
import rescuecore2.registry.Registry;
import rescuecore2.registry.MessageFactory;
import rescuecore2.registry.EntityFactory;
import rescuecore2.registry.PropertyFactory;
import rescuecore2.worldmodel.WorldModel;
import rescuecore2.worldmodel.Entity;
import rescuecore2.misc.Pair;
import rescuecore2.misc.MutableBoolean;
import rescuecore2.misc.CommandLineOptions;
import rescuecore2.misc.java.LoadableTypeProcessor;
import rescuecore2.misc.java.LoadableType;
import rescuecore2.Constants;
import rescuecore2.GUIComponent;
import rescuecore2.log.LogException;
import rescuecore2.log.Logger;
import rescuecore2.score.ScoreFunction;
import kernel.ui.KernelStartupPanel;
import kernel.ui.KernelGUI;
import kernel.ui.ScoreTable;
import kernel.ui.ScoreGraph;
/**
A class for launching the kernel.
*/
public final class StartKernel {
private static final String NO_STARTUP_MENU = "--nomenu";
private static final String NO_GUI = "--nogui";
private static final String AUTORUN = "--autorun";
private static final String GIS_MANIFEST_KEY = "Gis";
private static final String PERCEPTION_MANIFEST_KEY = "Perception";
private static final String COMMUNICATION_MANIFEST_KEY = "CommunicationModel";
private static final String COMMAND_COLLECTOR_KEY = "kernel.commandcollectors";
private static final String TERMINATION_KEY = "kernel.termination";
private static final String GIS_REGEX = "(.+WorldModelCreator).class";
private static final String PERCEPTION_REGEX = "(.+Perception).class";
private static final String COMMUNICATION_REGEX = "(.+CommunicationModel).class";
private static final LoadableType GIS_LOADABLE_TYPE = new LoadableType(GIS_MANIFEST_KEY, GIS_REGEX, WorldModelCreator.class);
private static final LoadableType PERCEPTION_LOADABLE_TYPE = new LoadableType(PERCEPTION_MANIFEST_KEY, PERCEPTION_REGEX, Perception.class);
private static final LoadableType COMMUNICATION_LOADABLE_TYPE = new LoadableType(COMMUNICATION_MANIFEST_KEY, COMMUNICATION_REGEX, CommunicationModel.class);
private static final String KERNEL_STARTUP_TIME_KEY = "kernel.startup.connect-time";
private static final String COMMAND_FILTERS_KEY = "kernel.commandfilters";
private static final String AGENT_REGISTRAR_KEY = "kernel.agents.registrar";
private static final String GUI_COMPONENTS_KEY = "kernel.ui.components";
/** Utility class: private constructor. */
private StartKernel() {}
/**
Start a kernel.
@param args Command line arguments.
*/
public static void main(String[] args) {
Config config = new Config();
boolean showStartupMenu = true;
boolean showGUI = true;
boolean autorun = false;
Logger.setLogContext("startup");
try {
args = CommandLineOptions.processArgs(args, config);
int i = 0;
for (String arg : args) {
if (arg.equalsIgnoreCase(NO_GUI)) {
showGUI = false;
}
else if (arg.equalsIgnoreCase(NO_STARTUP_MENU)) {
showStartupMenu = false;
}
else if (arg.equalsIgnoreCase(AUTORUN)) {
autorun = true;
}
else {
Logger.warn("Unrecognised option: " + arg);
}
}
// Process jar files
processJarFiles(config);
Registry localRegistry = new Registry("Kernel local registry");
// Register preferred message, entity and property factories
for (String next : config.getArrayValue(Constants.MESSAGE_FACTORY_KEY, "")) {
MessageFactory factory = instantiateFactory(next, MessageFactory.class);
if (factory != null) {
localRegistry.registerMessageFactory(factory);
Logger.info("Registered local message factory: " + next);
}
}
for (String next : config.getArrayValue(Constants.ENTITY_FACTORY_KEY, "")) {
EntityFactory factory = instantiateFactory(next, EntityFactory.class);
if (factory != null) {
localRegistry.registerEntityFactory(factory);
Logger.info("Registered local entity factory: " + next);
}
}
for (String next : config.getArrayValue(Constants.PROPERTY_FACTORY_KEY, "")) {
PropertyFactory factory = instantiateFactory(next, PropertyFactory.class);
if (factory != null) {
localRegistry.registerPropertyFactory(factory);
Logger.info("Registered local property factory: " + next);
}
}
// CHECKSTYLE:OFF:MagicNumber
config.addConstraint(new IntegerValueConstraint(Constants.KERNEL_PORT_NUMBER_KEY, 1, 65535));
// CHECKSTYLE:ON:MagicNumber
config.addConstraint(new IntegerValueConstraint(KERNEL_STARTUP_TIME_KEY, 0, Integer.MAX_VALUE));
config.addConstraint(new ClassNameSetValueConstraint(Constants.MESSAGE_FACTORY_KEY, MessageFactory.class));
config.addConstraint(new ClassNameSetValueConstraint(Constants.ENTITY_FACTORY_KEY, EntityFactory.class));
config.addConstraint(new ClassNameSetValueConstraint(Constants.PROPERTY_FACTORY_KEY, PropertyFactory.class));
config.addConstraint(new ClassNameSetValueConstraint(COMMAND_FILTERS_KEY, CommandFilter.class));
config.addConstraint(new ClassNameSetValueConstraint(TERMINATION_KEY, TerminationCondition.class));
config.addConstraint(new ClassNameSetValueConstraint(COMMAND_COLLECTOR_KEY, CommandCollector.class));
config.addConstraint(new ClassNameSetValueConstraint(GUI_COMPONENTS_KEY, GUIComponent.class));
config.addConstraint(new ClassNameValueConstraint(AGENT_REGISTRAR_KEY, AgentRegistrar.class));
config.addConstraint(new ClassNameValueConstraint(Constants.SCORE_FUNCTION_KEY, ScoreFunction.class));
Logger.setLogContext("kernel");
final KernelInfo kernelInfo = createKernel(config, showStartupMenu);
if (kernelInfo == null) {
System.exit(0);
}
KernelGUI gui = null;
if (showGUI) {
gui = new KernelGUI(kernelInfo.kernel, kernelInfo.componentManager, config, localRegistry, !autorun);
for (GUIComponent next : kernelInfo.guiComponents) {
gui.addGUIComponent(next);
if (next instanceof KernelListener) {
kernelInfo.kernel.addKernelListener((KernelListener)next);
}
}
JFrame frame = new JFrame("Kernel GUI");
frame.getContentPane().add(gui);
frame.pack();
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
kernelInfo.kernel.shutdown();
System.exit(0);
}
});
frame.setVisible(true);
}
initialiseKernel(kernelInfo, config, localRegistry);
autostartComponents(kernelInfo, localRegistry, gui, config);
if (!showGUI || autorun) {
waitForComponentManager(kernelInfo, config);
Kernel kernel = kernelInfo.kernel;
while (!kernel.hasTerminated()) {
kernel.timestep();
}
kernel.shutdown();
}
}
catch (ConfigException e) {
Logger.fatal("Couldn't start kernel", e);
}
catch (KernelException e) {
Logger.fatal("Couldn't start kernel", e);
}
catch (IOException e) {
Logger.fatal("Couldn't start kernel", e);
}
catch (LogException e) {
Logger.fatal("Couldn't write log", e);
}
catch (InterruptedException e) {
Logger.fatal("Kernel interrupted");
}
}
private static KernelInfo createKernel(Config config, boolean showMenu) throws KernelException {
KernelStartupOptions options = new KernelStartupOptions(config);
// Show the chooser GUI
if (showMenu) {
final JDialog dialog = new JDialog((Frame)null, "Setup kernel options", true);
KernelStartupPanel panel = new KernelStartupPanel(config, options);
JButton okButton = new JButton("OK");
JButton cancelButton = new JButton("Cancel");
JPanel buttons = new JPanel(new FlowLayout());
buttons.add(okButton);
buttons.add(cancelButton);
dialog.getContentPane().add(panel, BorderLayout.CENTER);
dialog.getContentPane().add(buttons, BorderLayout.SOUTH);
final MutableBoolean ok = new MutableBoolean(true);
okButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ok.set(true);
dialog.setVisible(false);
dialog.dispose();
}
});
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ok.set(false);
dialog.setVisible(false);
dialog.dispose();
}
});
dialog.pack();
dialog.setVisible(true);
if (!ok.get()) {
return null;
}
}
WorldModelCreator gis = options.getWorldModelCreator();
Perception perception = options.getPerception();
CommunicationModel comms = options.getCommunicationModel();
CommandFilter filter = makeCommandFilter(config);
TerminationCondition termination = makeTerminationCondition(config);
ScoreFunction score = makeScoreFunction(config);
CommandCollector collector = makeCommandCollector(config);
// Get the world model
WorldModel<? extends Entity> worldModel = gis.buildWorldModel(config);
// Create the kernel
ScoreGraph graph = new ScoreGraph(score);
Kernel kernel = new Kernel(config, perception, comms, worldModel, gis, filter, termination, graph, collector);
// Create the component manager
ComponentManager componentManager = new ComponentManager(kernel, worldModel, config);
KernelInfo result = new KernelInfo(kernel, options, componentManager, makeGUIComponents(config, componentManager, perception, comms, termination, filter, graph, collector, score));
return result;
}
private static void initialiseKernel(KernelInfo kernel, Config config, Registry registry) throws KernelException {
registerInitialAgents(config, kernel.componentManager, kernel.kernel.getWorldModel());
if (!config.getBooleanValue(KernelConstants.INLINE_ONLY_KEY, false)) {
// Start the connection manager
ConnectionManager connectionManager = new ConnectionManager();
try {
connectionManager.listen(config.getIntValue(Constants.KERNEL_PORT_NUMBER_KEY), registry, kernel.componentManager);
}
catch (IOException e) {
throw new KernelException("Couldn't open kernel port", e);
}
}
}
private static void waitForComponentManager(final KernelInfo kernel, Config config) throws KernelException {
// Wait for all connections
// Set up a CountDownLatch
final CountDownLatch latch = new CountDownLatch(1);
final long timeout = config.getIntValue(KERNEL_STARTUP_TIME_KEY, -1);
Thread timeoutThread = null;
if (timeout > 0) {
timeoutThread = new Thread() {
public void run() {
try {
Thread.sleep(timeout);
latch.countDown();
}
// CHECKSTYLE:OFF:EmptyBlock
catch (InterruptedException e) {
// Ignore
}
// CHECKSTYLE:ON:EmptyBlock
}
};
}
Thread waitThread = new Thread() {
public void run() {
try {
kernel.componentManager.waitForAllAgents();
kernel.componentManager.waitForAllSimulators();
kernel.componentManager.waitForAllViewers();
}
// CHECKSTYLE:OFF:EmptyBlock
catch (InterruptedException e) {
// Ignore
}
// CHECKSTYLE:ON:EmptyBlock
latch.countDown();
}
};
waitThread.start();
if (timeoutThread != null) {
timeoutThread.start();
}
// Wait at the latch until either everything is connected or the connection timeout expires
Logger.info("Waiting for all agents, simulators and viewers to connect.");
if (timeout > -1) {
Logger.info("Connection timeout is " + timeout + "ms");
}
try {
latch.await();
}
catch (InterruptedException e) {
waitThread.interrupt();
if (timeoutThread != null) {
timeoutThread.interrupt();
}
throw new KernelException("Interrupted");
}
}
private static void autostartComponents(KernelInfo info, Registry registry, KernelGUI gui, Config config) throws InterruptedException {
KernelStartupOptions options = info.options;
Collection<Callable<Void>> all = new ArrayList<Callable<Void>>();
Config launchConfig = new Config(config);
launchConfig.removeExcept(Constants.RANDOM_SEED_KEY, Constants.RANDOM_CLASS_KEY);
for (Pair<String, Integer> next : options.getInlineComponents()) {
if (next.second() > 0) {
all.add(new ComponentStarter(next.first(), info.componentManager, next.second(), registry, gui, launchConfig));
}
}
ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
service.invokeAll(all);
}
private static void registerInitialAgents(Config config, ComponentManager c, WorldModel<? extends Entity> model) throws KernelException {
AgentRegistrar ar = instantiate(config.getValue(AGENT_REGISTRAR_KEY), AgentRegistrar.class);
if (ar == null) {
throw new KernelException("Couldn't instantiate agent registrar");
}
ar.registerAgents(model, config, c);
}
private static CommandFilter makeCommandFilter(Config config) {
ChainedCommandFilter result = new ChainedCommandFilter();
List<String> classNames = config.getArrayValue(COMMAND_FILTERS_KEY, null);
for (String next : classNames) {
Logger.debug("Command filter found: '" + next + "'");
CommandFilter f = instantiate(next, CommandFilter.class);
if (f != null) {
result.addFilter(f);
}
}
return result;
}
private static TerminationCondition makeTerminationCondition(Config config) {
List<TerminationCondition> result = new ArrayList<TerminationCondition>();
for (String next : config.getArrayValue(TERMINATION_KEY, null)) {
TerminationCondition t = instantiate(next, TerminationCondition.class);
if (t != null) {
result.add(t);
}
}
return new OrTerminationCondition(result);
}
private static ScoreFunction makeScoreFunction(Config config) {
String className = config.getValue(Constants.SCORE_FUNCTION_KEY);
ScoreFunction result = instantiate(className, ScoreFunction.class);
return new ScoreTable(result);
}
private static CommandCollector makeCommandCollector(Config config) {
List<String> classNames = config.getArrayValue(COMMAND_COLLECTOR_KEY);
CompositeCommandCollector result = new CompositeCommandCollector();
for (String next : classNames) {
CommandCollector c = instantiate(next, CommandCollector.class);
if (c != null) {
result.addCommandCollector(c);
}
}
return result;
}
private static List<GUIComponent> makeGUIComponents(Config config, Object... objectsToTest) {
List<GUIComponent> result = new ArrayList<GUIComponent>();
List<String> classNames = config.getArrayValue(GUI_COMPONENTS_KEY, null);
for (String next : classNames) {
Logger.debug("GUI component found: '" + next + "'");
GUIComponent c = instantiate(next, GUIComponent.class);
if (c != null) {
result.add(c);
}
}
for (Object next : objectsToTest) {
if (next instanceof GUIComponent) {
result.add((GUIComponent)next);
}
}
return result;
}
private static void processJarFiles(Config config) throws IOException {
LoadableTypeProcessor processor = new LoadableTypeProcessor(config);
processor.addFactoryRegisterCallbacks(Registry.SYSTEM_REGISTRY);
processor.addConfigUpdater(LoadableType.AGENT, config, KernelConstants.AGENTS_KEY);
processor.addConfigUpdater(LoadableType.SIMULATOR, config, KernelConstants.SIMULATORS_KEY);
processor.addConfigUpdater(LoadableType.VIEWER, config, KernelConstants.VIEWERS_KEY);
processor.addConfigUpdater(LoadableType.COMPONENT, config, KernelConstants.COMPONENTS_KEY);
processor.addConfigUpdater(GIS_LOADABLE_TYPE, config, KernelConstants.GIS_KEY);
processor.addConfigUpdater(PERCEPTION_LOADABLE_TYPE, config, KernelConstants.PERCEPTION_KEY);
processor.addConfigUpdater(COMMUNICATION_LOADABLE_TYPE, config, KernelConstants.COMMUNICATION_MODEL_KEY);
Logger.info("Looking for gis, perception, communication, agent, simulator and viewer implementations");
processor.process();
}
private static class ComponentStarter implements Callable<Void> {
private String className;
private ComponentManager componentManager;
private int count;
private Registry registry;
private KernelGUI gui;
private Config config;
public ComponentStarter(String className, ComponentManager componentManager, int count, Registry registry, KernelGUI gui, Config config) {
this.className = className;
this.componentManager = componentManager;
this.count = count;
this.registry = registry;
this.gui = gui;
this.config = config;
Logger.debug("New ComponentStarter: " + className + " * " + count);
}
public Void call() throws InterruptedException {
Logger.debug("ComponentStarter running: " + className + " * " + count);
ComponentLauncher launcher = new InlineComponentLauncher(componentManager, config);
launcher.setDefaultRegistry(registry);
Logger.info("Launching " + count + " instances of component '" + className + "'...");
for (int i = 0; i < count; ++i) {
Component c = instantiate(className, Component.class);
if (c == null) {
break;
}
Logger.info("Launching " + className + " instance " + (i + 1) + "...");
try {
c.initialise();
launcher.connect(c);
if (gui != null && c instanceof GUIComponent) {
gui.addGUIComponent((GUIComponent)c);
}
Logger.info(className + "instance " + (i + 1) + " launched successfully");
}
catch (ComponentConnectionException e) {
Logger.info(className + "instance " + (i + 1) + " failed: " + e.getMessage());
break;
}
catch (ComponentInitialisationException e) {
Logger.info(className + "instance " + (i + 1) + " failed", e);
}
catch (ConnectionException e) {
Logger.info(className + "instance " + (i + 1) + " failed", e);
}
}
return null;
}
}
private static class KernelInfo {
Kernel kernel;
KernelStartupOptions options;
ComponentManager componentManager;
List<GUIComponent> guiComponents;
public KernelInfo(Kernel kernel, KernelStartupOptions options, ComponentManager componentManager, List<GUIComponent> otherComponents) {
this.kernel = kernel;
this.options = options;
this.componentManager = componentManager;
guiComponents = new ArrayList<GUIComponent>(otherComponents);
}
}
}