/***************************************************************************
* Copyright 2006-2016 by Christian Ihle *
* contact@kouchat.net *
* *
* This file is part of KouChat. *
* *
* KouChat is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version. *
* *
* KouChat 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 *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with KouChat. *
* If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
package net.usikkert.kouchat.misc;
import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import net.usikkert.kouchat.Constants;
import net.usikkert.kouchat.message.CoreMessages;
import net.usikkert.kouchat.net.FileReceiver;
import net.usikkert.kouchat.net.FileSender;
import net.usikkert.kouchat.net.FileToSend;
import net.usikkert.kouchat.net.FileTransfer;
import net.usikkert.kouchat.net.TransferList;
import net.usikkert.kouchat.settings.Settings;
import net.usikkert.kouchat.ui.UserInterface;
import net.usikkert.kouchat.util.DateTools;
import net.usikkert.kouchat.util.Tools;
import net.usikkert.kouchat.util.Validate;
import org.jetbrains.annotations.NonNls;
/**
* Parses and executes commands. A command starts with a slash, and can
* have arguments.
*
* @author Christian Ihle
*/
public class CommandParser {
private static final String WHITESPACE = "\\s"; // Any whitespace character
private final DateTools dateTools = new DateTools();
private final Controller controller;
private final UserInterface ui;
private final MessageController msgController;
private final User me;
private final TransferList tList;
private final Settings settings;
private final CoreMessages coreMessages;
/**
* Constructor.
*
* @param controller The controller.
* @param ui The user interface.
* @param settings The settings to use.
* @param coreMessages The messages to use.
*/
public CommandParser(final Controller controller, final UserInterface ui, final Settings settings,
final CoreMessages coreMessages) {
Validate.notNull(controller, "Controller can not be null");
Validate.notNull(ui, "UserInterface can not be null");
Validate.notNull(settings, "Settings can not be null");
Validate.notNull(coreMessages, "Core messages can not be null");
this.controller = controller;
this.ui = ui;
this.settings = settings;
this.coreMessages = coreMessages;
msgController = ui.getMessageController();
me = settings.getMe();
tList = controller.getTransferList();
}
/**
* Command: <em>/topic <optional new topic></em>.
*
* <p>Prints the current topic if no arguments are supplied,
* or changes the topic. To remove the topic, use a space as the argument.</p>
*
* @param args Nothing, or the new topic.
*/
private void cmdTopic(final String args) {
if (args.length() == 0) {
final Topic topic = controller.getTopic();
if (topic.getTopic().equals("")) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.topic.systemMessage.noTopic"));
}
else {
final String date = dateTools.dateToString(new Date(topic.getTime()),
coreMessages.getMessage("core.dateFormat.topic"));
msgController.showSystemMessage(coreMessages.getMessage("core.command.topic.systemMessage.topicIs",
topic.getTopic(), topic.getNick(), date));
}
}
else {
try {
fixTopic(args);
}
catch (final CommandException e) {
msgController.showSystemMessage(e.getMessage());
}
}
}
/**
* Command: <em>/away <away message></em>.
*
* <p>Set status to away.</p>
*
* @param args The away message.
*/
private void cmdAway(final String args) {
if (me.isAway()) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.away.systemMessage.alreadyAway",
me.getAwayMsg()));
}
else {
if (args.trim().length() == 0) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.away.systemMessage.missingArgument"));
}
else {
try {
controller.goAway(args.trim());
}
catch (final CommandException e) {
msgController.showSystemMessage(e.getMessage());
}
}
}
}
/**
* Command: <em>/back</em>.
*
* <p>Set status to not away.</p>
*/
private void cmdBack() {
if (me.isAway()) {
try {
controller.comeBack();
}
catch (final CommandException e) {
msgController.showSystemMessage(e.getMessage());
}
}
else {
msgController.showSystemMessage(coreMessages.getMessage("core.command.back.systemMessage.notAway"));
}
}
/**
* Command: <em>/clear</em>.
*
* <p>Clear all the text from the chat.</p>
*/
private void cmdClear() {
ui.clearChat();
}
/**
* Command: <em>/about</em>.
*
* <p>Show information about the application.</p>
*/
private void cmdAbout() {
msgController.showSystemMessage(coreMessages.getMessage("core.command.about.systemMessage.about",
Constants.APP_NAME, Constants.APP_VERSION,
Constants.AUTHOR_NAME, Constants.AUTHOR_MAIL,
Constants.APP_WEB));
}
/**
* Command: <em>/help</em>.
*
* <p>Shows a list of commands.</p>
*/
private void cmdHelp() {
showCommands();
}
/**
* Command: <em>/whois <nick></em>.
*
* <p>Show information about a user.</p>
*
* @param args The user to show information about.
*/
private void cmdWhois(final String args) {
if (args.trim().length() == 0) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.whois.systemMessage.missingArgument"));
}
else {
final String[] argsArray = args.split(WHITESPACE);
final String nick = argsArray[1].trim();
final User user = controller.getUser(nick);
if (user == null) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.whois.systemMessage.noSuchUser",
nick));
}
else {
String info;
if (user.isAway()) {
info = coreMessages.getMessage("core.command.whois.systemMessage.whois.away", user.getNick());
} else {
info = coreMessages.getMessage("core.command.whois.systemMessage.whois", user.getNick());
}
info += "\n" + coreMessages.getMessage("core.command.whois.ipAddress", user.getIpAddress());
if (user.getHostName() != null) {
info += "\n" + coreMessages.getMessage("core.command.whois.hostName", user.getHostName());
}
info += "\n" + coreMessages.getMessage("core.command.whois.client", user.getClient());
info += "\n" + coreMessages.getMessage("core.command.whois.operatingSystem", user.getOperatingSystem());
info += "\n" + coreMessages.getMessage("core.command.whois.online",
dateTools.howLongFromNow(user.getLogonTime()));
if (user.isAway()) {
info += "\n" + coreMessages.getMessage("core.command.whois.awayMessage", user.getAwayMsg());
}
msgController.showSystemMessage(info);
}
}
}
/**
* Command: <em>/send <nick> <file></em>.
*
* <p>Send a file to a user.</p>
*
* @param args First argument is the user to send to, and the second is the file to send to the user.
*/
private void cmdSend(final String args) {
final String[] argsArray = args.split(WHITESPACE);
if (argsArray.length <= 2) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.send.systemMessage.missingArguments"));
}
else {
final String nick = argsArray[1];
final User user = controller.getUser(nick);
if (user != me) {
if (user == null) {
msgController.showSystemMessage(
coreMessages.getMessage("core.command.send.systemMessage.noSuchUser", nick));
}
else {
String file = "";
for (int i = 2; i < argsArray.length; i++) {
file += argsArray[i] + " ";
}
file = file.trim();
final File sendFile = new File(file);
if (sendFile.exists() && sendFile.isFile()) {
try {
sendFile(user, new FileToSend(sendFile));
}
catch (final CommandException e) {
msgController.showSystemMessage(e.getMessage());
}
}
else {
msgController.showSystemMessage(
coreMessages.getMessage("core.command.send.systemMessage.noSuchFile", file));
}
}
}
else {
msgController.showSystemMessage(coreMessages.getMessage("core.command.send.systemMessage.userIsMe"));
}
}
}
/**
* Command: <em>/receive <nick> <id></em>.
*
* <p>Accept a file transfer request from a user and start the transfer.</p>
*
* @param args First argument is the other user in the file transfer, and the second is the id of the file transfer.
*/
private void cmdReceive(final String args) {
final String[] argsArray = args.split(WHITESPACE);
if (argsArray.length != 3) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.receive.systemMessage.missingArguments"));
return;
}
final String nick = argsArray[1];
final User user = controller.getUser(nick);
if (user == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.receive.systemMessage.noSuchUser", nick));
return;
}
if (user == me) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.receive.systemMessage.userIsMe"));
return;
}
final Integer id = parseFileTransferId(argsArray[2]);
if (id == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.receive.systemMessage.invalidFileId", argsArray[2]));
return;
}
final FileReceiver fileReceiver = tList.getFileReceiver(user, id);
if (fileReceiver == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.receive.systemMessage.noSuchFileIdForUser", id, nick));
return;
}
if (fileReceiver.isAccepted()) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.receive.systemMessage.alreadyReceiving", fileReceiver.getFileName(), nick));
return;
}
final File file = fileReceiver.getFile();
if (file.exists()) {
final File newFile = Tools.getFileWithIncrementedName(file);
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.receive.systemMessage.renamingFile", file.getName(), newFile.getName()));
fileReceiver.setFile(newFile);
}
fileReceiver.accept();
}
/**
* Command: <em>/reject <nick> <id></em>.
*
* <p>Reject a file transfer request from a user and abort the transfer.</p>
*
* @param args First argument is the other user in the file transfer, and the second is the id of the file transfer.
*/
private void cmdReject(final String args) {
final String[] argsArray = args.split(WHITESPACE);
if (argsArray.length != 3) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.reject.systemMessage.missingArguments"));
return;
}
final String nick = argsArray[1];
final User user = controller.getUser(nick);
if (user == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.reject.systemMessage.noSuchUser", nick));
return;
}
if (user == me) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.reject.systemMessage.userIsMe"));
return;
}
final Integer id = parseFileTransferId(argsArray[2]);
if (id == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.reject.systemMessage.invalidFileId", argsArray[2]));
return;
}
final FileReceiver fileReceiver = tList.getFileReceiver(user, id);
if (fileReceiver == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.reject.systemMessage.noSuchFileIdForUser", id, nick));
return;
}
if (fileReceiver.isAccepted()) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.reject.systemMessage.alreadyReceiving", fileReceiver.getFileName(), nick));
return;
}
fileReceiver.reject();
}
/**
* Command: <em>/cancel <nick> <id></em>.
*
* <p>Cancel an ongoing file transfer with a user.</p>
*
* @param args First argument is the other user in the file transfer, and the second is the id of the file transfer.
*/
private void cmdCancel(final String args) {
final String[] argsArray = args.split(WHITESPACE);
if (argsArray.length != 3) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.cancel.systemMessage.missingArguments"));
return;
}
final String nick = argsArray[1];
final User user = controller.getUser(nick);
if (user == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.cancel.systemMessage.noSuchUser", nick));
return;
}
if (user == me) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.cancel.systemMessage.userIsMe"));
return;
}
final Integer id = parseFileTransferId(argsArray[2]);
if (id == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.cancel.systemMessage.invalidFileId", argsArray[2]));
return;
}
final FileTransfer fileTransfer = tList.getFileTransfer(user, id);
if (fileTransfer == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.cancel.systemMessage.noSuchFileIdForUser", id, nick));
return;
}
if (fileTransfer instanceof FileReceiver) {
final FileReceiver fileReceiver = (FileReceiver) fileTransfer;
if (!fileReceiver.isAccepted()) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.cancel.systemMessage.notStartedYet", fileReceiver.getFileName(), nick));
return;
}
}
cancelFileTransfer(fileTransfer);
}
private Integer parseFileTransferId(final String argument) {
try {
return Integer.parseInt(argument);
} catch (final NumberFormatException e) {
return null;
}
}
/**
* Command: <em>/msg <nick> <msg></em>.
*
* <p>Send a private message to a user.</p>
*
* @param args The first argument is the user to send to, and the second is the private message to the user.
*/
private void cmdMsg(final String args) {
final String[] argsArray = args.split(WHITESPACE);
if (argsArray.length <= 2) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.msg.systemMessage.missingArguments"));
}
else {
final String nick = argsArray[1];
final User user = controller.getUser(nick);
if (user == null) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.msg.systemMessage.noSuchUser", nick));
}
else if (user == me) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.msg.systemMessage.userIsMe"));
}
else if (settings.isNoPrivateChat()) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.msg.systemMessage.privateChatDisabled"));
}
else if (user.getPrivateChatPort() == 0) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.msg.systemMessage.noPrivateChatPortNumber", user.getNick()));
}
else {
String privmsg = "";
for (int i = 2; i < argsArray.length; i++) {
privmsg += argsArray[i] + " ";
}
privmsg = privmsg.trim();
try {
controller.sendPrivateMessage(privmsg, user);
msgController.showPrivateOwnMessage(user, privmsg);
}
catch (final CommandException e) {
msgController.showSystemMessage(e.getMessage());
}
}
}
}
/**
* Command: <em>/nick <new nick></em>.
*
* <p>Changes your nick name.</p>
*
* @param args The nick to change to.
*/
private void cmdNick(final String args) {
if (args.trim().length() == 0) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.nick.systemMessage.missingArgument"));
}
else {
final String[] argsArray = args.split(WHITESPACE);
final String nick = argsArray[1].trim();
if (!nick.equals(me.getNick())) {
if (controller.isNickInUse(nick)) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.nick.systemMessage.nickInUse", nick));
}
else if (!Tools.isValidNick(nick)) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.nick.systemMessage.nickInvalid", nick));
}
else {
try {
controller.changeMyNick(nick);
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.nick.systemMessage.nickChanged", me.getNick()));
ui.showTopic();
}
catch (final CommandException e) {
msgController.showSystemMessage(e.getMessage());
}
}
}
else {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.nick.systemMessage.nickIdentical", nick));
}
}
}
/**
* Command: <em>/users</em>.
*
* <p>Shows a list of connected users.</p>
*/
private void cmdUsers() {
final UserList list = controller.getUserList();
String userList = "";
for (int i = 0; i < list.size(); i++) {
final User user = list.get(i);
userList += user.getNick();
if (i < list.size() - 1) {
userList += ", ";
}
}
msgController.showSystemMessage(coreMessages.getMessage("core.command.users.systemMessage.users", userList));
}
/**
* Command: <em>/transfers</em>.
*
* <p>Shows a list of all transfers and their status.</p>
*/
private void cmdTransfers() {
final List<FileSender> fsList = tList.getFileSenders();
final List<FileReceiver> frList = tList.getFileReceivers();
final StringBuilder transferInfo = new StringBuilder();
if (fsList.size() > 0) {
transferInfo.append("\n");
transferInfo.append(coreMessages.getMessage("core.command.transfers.sending"));
for (final FileSender fs : fsList) {
appendTransferInfo(fs, transferInfo);
}
}
if (frList.size() > 0) {
transferInfo.append("\n");
transferInfo.append(coreMessages.getMessage("core.command.transfers.receiving"));
for (final FileReceiver fr : frList) {
appendTransferInfo(fr, transferInfo);
}
}
if (transferInfo.length() == 0) {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.transfers.systemMessage.noFileTransfers"));
} else {
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.transfers.systemMessage.activeFileTransfers") + transferInfo.toString());
}
}
/**
* Command: <em>/quit</em>.
*
* <p>Quits the application.</p>
*/
private void cmdQuit() {
ui.quit();
}
/**
* Adds a new line with information about the file transfer.
*
* @param fileTransfer The file transfer to add info about.
* @param transferInfo The string builder to add the info to.
*/
private void appendTransferInfo(final FileTransfer fileTransfer, final StringBuilder transferInfo) {
transferInfo.append("\n ");
final int fileTransferId = fileTransfer.getId();
final String fileName = fileTransfer.getFileName();
final String fileSize = Tools.byteToString(fileTransfer.getFileSize());
final int percent = fileTransfer.getPercent();
final String speed = Tools.byteToString(fileTransfer.getSpeed());
final String user = fileTransfer.getUser().getNick();
if (fileTransfer.getDirection() == FileTransfer.Direction.SEND) {
transferInfo.append(coreMessages.getMessage("core.command.transfers.sendingFile",
fileTransferId, fileName, fileSize, percent, speed, user));
} else {
transferInfo.append(coreMessages.getMessage("core.command.transfers.receivingFile",
fileTransferId, fileName, fileSize, percent, speed, user));
}
}
/**
* Command: <em>//<text></em>.
*
* <p>Sends the text as a message, instead of parsing it as a command.</p>
*
* @param line The text starting with a slash.
*/
private void cmdSlash(final String line) {
final String message = line.replaceFirst("/", "");
try {
controller.sendChatMessage(message);
msgController.showOwnMessage(message);
}
catch (final CommandException e) {
msgController.showSystemMessage(e.getMessage());
}
}
/**
* Command: <em>/'anything'</em>.
*
* <p>The command was not recognized by the parser.</p>
*
* @param command The unknown command.
*/
private void cmdUnknown(final String command) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.unknown.systemMessage.unknown", command));
}
/**
* Updates the topic. If the new topic is empty, the topic will be removed.
*
* @param newTopic The new topic to use.
* @throws CommandException If there was a problem changing the topic.
*/
public void fixTopic(final String newTopic) throws CommandException {
final Topic topic = controller.getTopic();
final String trimTopic = newTopic.trim();
if (!trimTopic.equals(topic.getTopic().trim())) {
controller.changeTopic(trimTopic);
if (trimTopic.length() > 0) {
msgController.showSystemMessage(coreMessages.getMessage("core.command.topic.systemMessage.topicChanged",
trimTopic));
} else {
msgController.showSystemMessage(coreMessages.getMessage("core.command.topic.systemMessage.topicRemoved"));
}
ui.showTopic();
}
}
/**
* Sends a file to a user.
*
* @param user The user to send to.
* @param file The file to send to the user.
* @throws CommandException If there was a problem sending the file.
*/
public void sendFile(final User user, final FileToSend file) throws CommandException {
controller.sendFile(user, file);
final FileSender fileSend = tList.addFileSender(user, file);
ui.showTransfer(fileSend);
final String size = Tools.byteToString(file.length());
msgController.showSystemMessage(coreMessages.getMessage(
"core.command.send.systemMessage.sendingFile",
file.getName(), fileSend.getId(), size, user.getNick()));
}
/**
* Cancels a file transfer, even if the file transfer has not been
* answered by the other user yet.
*
* @param fileTransfer The file transfer to cancel.
*/
public void cancelFileTransfer(final FileTransfer fileTransfer) {
fileTransfer.cancel();
if (fileTransfer instanceof FileSender) {
final FileSender fs = (FileSender) fileTransfer;
// This means that the other user has not answered yet
if (fs.isWaiting()) {
final FileToSend file = fs.getFile();
final User user = fs.getUser();
msgController.showSystemMessage(coreMessages.getMessage("core.command.cancel.systemMessage.cancelled",
file.getName(), user.getNick()));
tList.removeFileSender(fs);
controller.sendFileAbort(user, file.hashCode(), file.getName());
}
}
}
/**
* Shows a list of all the supported commands, with a short description.
*/
public void showCommands() {
msgController.showSystemMessage(
coreMessages.getMessage("core.command.help.systemMessage.commands", Constants.APP_NAME) + "\n" +
coreMessages.getMessage("core.command.about.systemMessage.help", Constants.APP_NAME) + "\n" +
coreMessages.getMessage("core.command.away.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.back.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.cancel.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.clear.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.help.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.msg.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.nick.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.quit.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.receive.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.reject.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.send.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.topic.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.transfers.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.users.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.whois.systemMessage.help") + "\n" +
coreMessages.getMessage("core.command.slash.systemMessage.help"));
}
/**
* Parses the line to split the command from the arguments.
*
* <p>The command is then checked against valid options and redirected to the appropriate method.</p>
*
* @param line The command in its raw form.
*/
public void parse(final String line) {
@NonNls final String command;
if (line.contains(" ")) {
command = line.substring(1, line.indexOf(' '));
} else {
command = line.substring(1, line.length());
}
if (command.length() > 0) {
final String args = line.replaceFirst("/" + Pattern.quote(command), "");
if (command.equals("topic")) {
cmdTopic(args);
} else if (command.equals("away")) {
cmdAway(args);
} else if (command.equals("back")) {
cmdBack();
} else if (command.equals("clear")) {
cmdClear();
} else if (command.equals("about")) {
cmdAbout();
} else if (command.equals("help")) {
cmdHelp();
} else if (command.equals("whois")) {
cmdWhois(args);
} else if (command.equals("send")) {
cmdSend(args);
} else if (command.equals("receive")) {
cmdReceive(args);
} else if (command.equals("reject")) {
cmdReject(args);
} else if (command.equals("cancel")) {
cmdCancel(args);
} else if (command.equals("msg")) {
cmdMsg(args);
} else if (command.equals("nick")) {
cmdNick(args);
} else if (command.equals("users")) {
cmdUsers();
} else if (command.equals("transfers")) {
cmdTransfers();
} else if (command.equals("quit")) {
cmdQuit();
} else if (command.startsWith("/")) {
cmdSlash(line);
} else {
cmdUnknown(command);
}
}
else {
cmdUnknown(command);
}
}
}