ca.wumbo.doommanager.client.controller.ConsoleController.java Source code

Java tutorial

Introduction

Here is the source code for ca.wumbo.doommanager.client.controller.ConsoleController.java

Source

/*
 * DoomManager
 * Copyright (C) 2014  Chris K
 * 
 * This program 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 ca.wumbo.doommanager.client.controller;

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;

import javax.annotation.PostConstruct;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import ca.wumbo.doommanager.Start;
import ca.wumbo.doommanager.client.util.Controllable;
import ca.wumbo.doommanager.client.util.SelfInjectableController;
import ca.wumbo.doommanager.net.NetworkReceiver;
import ca.wumbo.doommanager.net.NetworkSelector;
import ca.wumbo.doommanager.server.Server;
import ca.wumbo.doommanager.server.ServerManager;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;

/**
 * The controller for the console, which accepts text and allows the user to
 * issue commands.
 */
public class ConsoleController extends SelfInjectableController implements Controllable {

    /**
     * The max amount of commands to remember.
     */
    public static final int MAX_COMMANDS_REMEMBERED = 128;

    /**
     * The pointer to the last command the user was at.
     */
    private int commandBufferIndex = 0;

    /**
     * Contains a list of sent commands that can be recalled by pressing the up
     * arrow.
     */
    private ArrayList<String> commandBuffer = new ArrayList<>(MAX_COMMANDS_REMEMBERED);

    @Autowired
    private CoreController coreController;

    @Autowired
    private ServerManager serverManager;

    @Autowired
    private NetworkSelector clientSelector;

    @Value("${console.controller.fxmlpath}")
    private String fxmlPath;

    @Value("${doommanager.title}")
    private String applicationTitle;

    @Value("${doommanager.version}")
    private String applicationVersion;

    //=========================================================================

    @FXML
    private BorderPane rootBorderPane;

    @FXML
    private TextArea textArea;

    @FXML
    private TextField textField;

    //=========================================================================

    /**
     * The logger for this class.
     */
    private static final Logger log = LogManager.getLogger(ConsoleController.class);

    /**
     * Only to be instantiated by Spring.
     */
    private ConsoleController() {
    }

    /**
     * Loads the FXML data and injects it into this object. Should be called by
     * Spring right after the constructor and dependencies are linked. To 
     * reduce code duplication, this functionality was moved to a containing
     * class.
     * 
     * @throws NullPointerException
     *       If the FXML path is null.
     * 
     * @throws RuntimeException
     *       If the FXML file is missing or corrupt.
     */
    @PostConstruct
    public void loadFXML() {
        super.loadFXML(fxmlPath);
    }

    @FXML
    private void initialize() {
        textField.setOnKeyPressed(event -> {
            // Submit any text on enter.
            if (event.getCode().equals(KeyCode.ENTER))
                submitLineToConsole();

            // If we press up/down, look for lines we stored.
            if (event.getCode().equals(KeyCode.UP) || event.getCode().equals(KeyCode.DOWN)) {
                if (commandBuffer.size() > 0) {
                    commandBufferIndex += (event.getCode().equals(KeyCode.UP) ? -1 : 1);
                    commandBufferIndex = Math.min(Math.max(0, commandBufferIndex), commandBuffer.size());
                    textField.setText(commandBufferIndex == commandBuffer.size() ? ""
                            : commandBuffer.get(commandBufferIndex));
                    textField.end();
                    event.consume(); // Has to be consumed or else it will mess up the end() caret setting.
                } else {
                    textField.setText("");
                }
            }
        });
    }

    /**
     * Takes whatever is in the text field and submits it.
     */
    private void submitLineToConsole() {
        // Trim the string before using, also nulls shouldn't happen (but just in case).
        String text = textField.getText().trim().replace('\0', '?');

        // Ignore empty lines.
        if (text.isEmpty())
            return;

        // Remember what we submitted, most recent goes first.
        commandBuffer.add(text);

        // Reset due to submission of text.
        commandBufferIndex = commandBuffer.size();

        // Handle commands.
        switch (text.toLowerCase()) {
        case "clear":
            textArea.clear();
            break;

        case "connect": // Note: Debugging only.
            textArea.appendText("Starting connection to local host..." + System.lineSeparator());
            Optional<SelectionKey> key = clientSelector.openTCPConnection(
                    new InetSocketAddress("localhost", Server.DEFAULT_LISTEN_PORT), new NetworkReceiver() {
                        @Override
                        public void signalConnectionTerminated() {
                            System.out.println("Test connection killed.");
                        }

                        @Override
                        public void receiveData(byte[] data) {
                            System.out.println("Got data: " + data.length + " = " + Arrays.toString(data));
                        }
                    });
            if (key.isPresent()) {
                SelectionKey k = key.get();
                Platform.runLater(() -> {
                    try {
                        Thread.sleep(2000);
                        clientSelector.writeData(k, new byte[] { 1, 2 });
                        System.out.println("Go forth...");
                    } catch (Exception e) {
                        System.err.println("UGH");
                        e.printStackTrace();
                    }
                });
            }
            textArea.appendText("Connection made = " + key.isPresent() + System.lineSeparator());
            break;

        case "copy":
            try {
                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                StringSelection stringSelection = new StringSelection(textArea.getText());
                clipboard.setContents(stringSelection, stringSelection);
                textArea.appendText("Copied to clipboard." + System.lineSeparator());
            } catch (Exception e) {
                textArea.appendText("Error: Unable to get system clipboard." + System.lineSeparator());
            }
            break;

        case "exit":
            coreController.exit();
            break;

        case "getconnections":
            int numKeys = clientSelector.getNumConnections();
            textArea.appendText("There " + (numKeys != 1 ? "are " : "is ") + numKeys + " key"
                    + (numKeys != 1 ? "s." : ".") + System.lineSeparator());
            break;

        case "help":
            textArea.appendText("Commands:" + System.lineSeparator());
            textArea.appendText("    clear - Clears the console" + System.lineSeparator());
            textArea.appendText("    copy - Copies all the content to your clipboard" + System.lineSeparator());
            textArea.appendText("    exit - Exits the program" + System.lineSeparator());
            textArea.appendText("    getconnections - Lists the connections available" + System.lineSeparator());
            textArea.appendText("    help - Shows this help" + System.lineSeparator());
            textArea.appendText("    history - Prints command history for " + MAX_COMMANDS_REMEMBERED + " entries"
                    + System.lineSeparator());
            textArea.appendText("    memory - Get current memory statistics" + System.lineSeparator());
            textArea.appendText("    version - Gets the version information" + System.lineSeparator());
            break;

        case "history":
            if (commandBuffer.size() <= 1) {
                textArea.appendText("No history to display" + System.lineSeparator());
                break;
            }
            textArea.appendText("History:" + System.lineSeparator());
            for (int i = 0; i < commandBuffer.size() - 1; i++)
                textArea.appendText("    " + commandBuffer.get(i) + System.lineSeparator()); // Print everything but the last command (which was this).
            break;

        case "memory":
            NumberFormat nf = NumberFormat.getInstance();
            nf.setMaximumFractionDigits(2);
            nf.setMinimumFractionDigits(2);
            long totalMem = Runtime.getRuntime().totalMemory();
            long usedMem = totalMem - Runtime.getRuntime().freeMemory();
            long maxMem = Runtime.getRuntime().maxMemory();
            double megabyte = 1024.0 * 1024.0;
            textArea.appendText("Memory statistics:" + System.lineSeparator());
            textArea.appendText(
                    "    Memory used: " + nf.format(Math.abs(((double) usedMem / (double) maxMem)) * 100.0) + "%"
                            + System.lineSeparator());
            textArea.appendText(
                    "    " + nf.format(usedMem / megabyte) + " mb (used memory)" + System.lineSeparator());
            textArea.appendText(
                    "    " + nf.format(maxMem / megabyte) + " mb (max memory)" + System.lineSeparator());
            break;

        // DEBUG
        case "startserver":
            if (Start.getParsedRuntimeArgs().isClientServer()) {
                if (!serverManager.isInitialized()) {
                    try {
                        textArea.appendText("Starting server on port " + Server.DEFAULT_LISTEN_PORT + "."
                                + System.lineSeparator());
                        serverManager.initialize();
                    } catch (Exception e) {
                        textArea.appendText("Unable to initialize server." + System.lineSeparator());
                        textArea.appendText("    " + e.getMessage() + System.lineSeparator());
                    }
                }

                if (serverManager.isInitialized()) {
                    if (!serverManager.isRunning()) {
                        textArea.appendText("Running server on port " + Server.DEFAULT_LISTEN_PORT + "."
                                + System.lineSeparator());
                        try {
                            new Thread(serverManager, "ClientConsoleServer").start();
                            textArea.appendText("Successfully set up server." + System.lineSeparator());
                        } catch (Exception e) {
                            textArea.appendText("Error setting up server:" + System.lineSeparator());
                            textArea.appendText("    " + e.getMessage() + System.lineSeparator());
                        }
                    } else {
                        textArea.appendText("Server is already running." + System.lineSeparator());
                    }
                } else {
                    textArea.appendText("Server not initialized, cannot run." + System.lineSeparator());
                }
            }
            break;

        // DEBUG
        case "stopserver":
            if (Start.getParsedRuntimeArgs().isClientServer()) {
                if (serverManager.isInitialized() && serverManager.isRunning()) {
                    serverManager.requestServerTermination();
                    textArea.appendText("Requested server termination." + System.lineSeparator());
                } else {
                    textArea.appendText(
                            "Cannot stop server, it has not been initialized or started." + System.lineSeparator());
                }
            }
            break;

        case "version":
            textArea.appendText(applicationTitle + " version: " + applicationVersion + System.lineSeparator());
            break;

        default:
            textArea.appendText("Unknown command: " + text + System.lineSeparator());
            textArea.appendText("Type 'help' for commands" + System.lineSeparator());
            break;
        }

        textField.clear();
    }

    /**
     * Adds text to the textbox.
     * 
     * @param text
     *       The text to add. This should not be null (if so, a warning will be
     *       emitted) and will be discarded if it is.
     */
    public void addText(String text) {
        if (text == null) {
            log.warn("Passed a null text log message to the console.");
            return;
        }
        textArea.appendText(text + System.lineSeparator());
    }

    @Override
    public Pane getRootPane() {
        return rootBorderPane;
    }
}