package domain;
import gui.GUIController;
import gui.ComponentSwitcher.ComponentChoice;
import java.io.IOException;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.Map.Entry;
import javax.swing.JOptionPane;
import server.ServerController;
/**
* Met de IRCController worden alle kanalen beheert.
* De klasse is zowel een Observer (server) als een Observable (gui).
* @author David Covemaeker, Maarten Minnebo, Tim Van Thuyne, Toon Kint
*/
public class IRCController extends Observable implements Observer
{
private ServerController serverController;
private String oldLogin = "***";
private String login;
private HashMap<String, IRCChannel> channelHashMap;
private GUIController guiController;
/**
* De constructor van de IRCController maakt de channelHashMap aan, die een HashMap is waar alle kanalen in terecht komen.
* De HashMap bevat als sleutel een String die de naam van het kanaal bevat en het IRCChannel die aan de naam wordt gelinkt.
*/
public IRCController(GUIController guic)
{
this.channelHashMap = new HashMap<String, IRCChannel>();
guiController = guic;
}
/**
* Maakt ServerController aan, die op zijn beurt een ServerReader en -Writer object aanmaakt.
* De IRCController wordt observer van de ServerReader.
* @param server De gebruikte irc server ("irc.quakenet.org")
* @param login Loginnaam van de gebruiker
* @param port De gebruikte poort (6667)
*/
public boolean connect (String server, String login, int port) throws IOException
{
this.login = login;
serverController = new ServerController(server, login, port);
if(serverController.getSocket() != null)
{
serverController.getServerReader().addObserver(this);
return true;
}
else
return false;
}
/**
* Verbreekt de connectie met de server.
* @throws IOException
*/
public void disconnect() throws IOException
{
if(!channelHashMap.isEmpty())
{
channelHashMap.clear();
serverController.writeLine("QUIT :Je suis chemin!");
serverController.closeSocket();
}
}
public void changeNick(String newNick) throws IOException
{
serverController.writeLine(":" + login + " NICK " + newNick);
oldLogin = login;
login = newNick;
}
/**
* Methode update van de interface Observer, wordt opgeroepen als ServerReader tekst ontvangt van de server.
* Bepaalt wat voor soort bericht (message) het is en hoe erop gereageerd moet worden.
* De reacties zijn a.d.h.v. de Request for Comments 2812. De IRC Protocol.
* @param arg0 Het observable object
* @param arg1 Het argument dat wordt doorgegeven aan de notifyObservers methode
*/
@Override
public void update(Observable arg0, Object arg1)
{
// TODO Auto-generated method stub
String message = (String) arg1;
try
{
if (
!pingMessage(message) && !joinMessage(message) && !partMessage(message) && !kickMessage(message) && !nickMessage(message) &&
!quitMessage(message) && !VersionMessage(message) && !loginError(message) && !userListControl(message) &&
!MotdMessage(message) && !throttled(message) && message.contains(" PRIVMSG ")
)
{
putLineInChannel(message);
}
}
catch (IllegalArgumentException iae)
{
JOptionPane.showMessageDialog(guiController.getMainFrame().getContentPane(), iae.getMessage(), "Loginnaam niet beschikbaar", JOptionPane.WARNING_MESSAGE);
if(!(message.contains(oldLogin) && message.contains(login)))
guiController.getMainFrame().switchToComponent(guiController, ComponentChoice.StartScherm);
}
catch (ThrottledException te)
{
JOptionPane.showMessageDialog(guiController.getMainFrame().getContentPane(), te.getMessage(), "Server weigert verbinding", JOptionPane.ERROR_MESSAGE);
guiController.getMainFrame().switchToComponent(guiController, ComponentChoice.StartScherm);
}
catch (IOException e)
{
JOptionPane.showMessageDialog(guiController.getMainFrame().getContentPane(), "De verbinding is gestopt.", "Verbindingsfout", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
/**
* Maakt in alle controles gebruik van:
* EXCEL "!"
* COLON ":"
* POUND "#"
*/
/**
* Plaats een gewone geschreven zin in de channel.
* @param line De ingegeven lijn
*/
private void putLineInChannel(String line)
{
// TODO Auto-generated method stub
int indexOfExcl = line.indexOf("!");
String nickname = line.substring(1, indexOfExcl);
line = line.substring(1);
int indexOfColon = line.indexOf(":");
int indexOfPound = line.indexOf("#");
String channel;
try
{
channel = line.substring(indexOfPound, indexOfColon-1);
}
catch(StringIndexOutOfBoundsException e)
{
channel = nickname;
}
String message = line.substring(indexOfColon+1);
try
{
channelHashMap.get(channel.toLowerCase()).addChatMessage(nickname, message);
}
catch(NullPointerException e)
{
channelHashMap.put(channel.toLowerCase(), new IRCChannel(login));
setChanged();
notifyObservers(nickname);
channelHashMap.get(channel.toLowerCase()).addChatMessage(nickname, message);
}
}
/**
* Schrijft een chatbericht naar het meegegeven kanaal.
* @param channel Kanaal waarin bericht is ingegeven
* @param line Het ingegeven bericht
*/
public void writeToChannel(String channel, String line) throws IOException
{
serverController.sayLine(channel, line);
try
{
channelHashMap.get(channel.toLowerCase()).addChatMessage(login, line);
}
catch (NullPointerException e){}
}
/**
* @param channel Gewenst kanaalnaam.
* @return IRCChannel die gelinkt is aan de naam die de paramater channel bevat
*/
public IRCChannel getIRCChannel(String channel)
{
return channelHashMap.get(channel.toLowerCase());
}
/**
* Client aan kanaal toevoegen.
* Nieuw object wordt toevoegt aan de hashmap.
* Schrijft een speciaal bericht (JOIN) naar de server.
* @param channel Gewenste kanaal
* @throws Exception Als client al in het kanaal zit
*/
public void joinChannel(String channel) throws Exception
{
if (!channelHashMap.containsKey(channel.toLowerCase()))
{
serverController.writeLine("JOIN " + channel);
channelHashMap.put(channel.toLowerCase(), new IRCChannel(login));
}
else
{
throw new Exception("Client zit al in kanaal.");
}
}
/**
* Verlaat kanaal.
* Object wordt uit hashmap verwijdert.
* Schrijft een speciaal bericht (PART) naar de server.
* @param channel Kanaal dat client wil verlaten.
*/
public void leaveChannel(String channel) throws IOException
{
if (channelHashMap.containsKey(channel.toLowerCase()))
{
channelHashMap.remove(channel.toLowerCase());
if (channel.startsWith("#"))
serverController.writeLine("PART " + channel);
}
}
/**
* Een privaat kanaal wordt geopent tussen twee mensen.
* @param nickname De naam van de client waarmee je een privaat gesprek voert
* @throws Exception Als je al een privaat kanaal open hebt staan met die persoon
*/
public void startPrivateChat(String nickname) throws Exception
{
if (!channelHashMap.containsKey(nickname.toLowerCase()))
{
channelHashMap.put(nickname.toLowerCase(), new IRCChannel(nickname));
}
else
{
throw new Exception("Je bent alreeds in private chat met die persoon!");
}
}
/**
* Command: PING (3.7.2.) en PONG (3.7.3.)
* Nagaan of de verzonden lijn een PING bericht is. Indien dit het geval is moet er gereageerd worden met een PONG bericht.
* In de lijn wordt PING dan vervangen door PONG.
* @param line De ontvangen lijn
* @return match True als de lijn een PING bericht was, anders false
*/
private boolean pingMessage(String line) throws IOException
{
boolean match = false;
if (line.toUpperCase().startsWith("PING"))
{
serverController.writeLine("PONG" + line.substring(4));
match = true;
}
return match;
}
/**
* Command: JOIN (3.2.1.)
* Kijken of de verzonden lijn een JOIN bericht is.
* Het JOIN bericht van JAVA1 op kanaal #HoGent is als volgt:
* :JAVA1!jto@tolsun.oulu.fi JOIN #HoGent
* @param line De ontvangen lijn
* @return match True als boodschap een JOIN is, anders false
*/
private boolean joinMessage(String line)
{
boolean match = false;
if (line.contains(" JOIN ") && !line.contains("PRIVMSG"))
{
int indexOfExcl = line.indexOf("!");
String nickname = line.substring(1, indexOfExcl);
int indexOfPound = line.indexOf("#");
String channel = line.substring(indexOfPound);
channelHashMap.get(channel.toLowerCase()).addClientJoinedChannelMessage(nickname);
channelHashMap.get(channel.toLowerCase()).addUserToListModel(nickname);
match = true;
}
return match;
}
/**
* Command: PART (3.2.2.)
* Kijken of de verzonden lijn een PART bericht is.
* Het PART bericht van JAVA1 die kanaal #HoGent verlaat met bericht "Ik ben weg.", is als volgt:
* :JAVA1!jto@tolsun.oulu.fi PART #HoGent :Ik ben weg.
* --> controleren op dubbelpunt (:), als die er staat geeft user een reden van vertrek.
* @param line De ontvangen lijn
* @return match True als boodschap een PART is, anders false
*/
private boolean partMessage(String line)
{
boolean match = false;
if (line.contains(" PART ") && !line.contains("PRIVMSG") && !line.contains(login))
{
int indexOfExcl = line.indexOf("!");
String nickname = line.substring(1, indexOfExcl);
String channel = "";
String reason = "";
int indexOfPound = line.indexOf("#");
line = line.substring(1);
if (line.contains(":"))
{
int indexOfColon = line.indexOf(":");
channel = line.substring(indexOfPound-1, indexOfColon-1);
reason = line.substring(indexOfColon+1);
}
else
{
channel = line.substring(indexOfPound-1);
}
System.out.println("|" + channel + "|" + nickname + "|" + reason + "|");
channelHashMap.get(channel.toLowerCase()).addClientPartChannelMessage(nickname, reason);
channelHashMap.get(channel.toLowerCase()).removeUserFromListModel(nickname);
match = true;
}
return match;
}
/**
* Command: KICK (3.2.8.)
* Kijken of de verzonden lijn een KICK bericht is.
* KICK command zorgt ervoor dat iemand gedwongen uit het kanaal wordt verwijdert.
* Het KICK bericht van JAVA1 op kanaal #HoGent om COBOL te verwijderen uit het kanaal #HoGent, is als volgt:
* :JAVA1!jto@tolsun.oulu.fi KICK #HoGent COBOL :De reden van vertrek.
* --> controleren op dubbelpunt (:), als die er staat is er een reden voor de kick.
* @param line De ontvangen lijn
* @return match True als boodschap een KICK is, anders false
*/
private boolean kickMessage(String line)
{
boolean match = false;
if (line.contains(" KICK ") && !line.contains("PRIVMSG"))
{
String channel = "";
String reason = "";
String channelVictim = "";
int indexOfSpace;
line = line.substring(1);
int indexOfExcl = line.indexOf("!");
String kicker = line.substring(0, indexOfExcl);
int indexOfPound = line.indexOf("#");
if (line.contains(":"))
{
int indexOfColon = line.indexOf(":");
reason = line.substring(indexOfColon+1);
channelVictim = line.substring(indexOfPound, indexOfColon-1);
indexOfSpace = channelVictim.indexOf(" ");
channel = channelVictim.substring(0, indexOfSpace);
}
else
{
channelVictim = line.substring(indexOfPound-1);
indexOfSpace = channelVictim.indexOf(" ");
channel = channelVictim.substring(0, indexOfSpace);
}
String victim = channelVictim.substring(indexOfSpace+1);
channelHashMap.get(channel.toLowerCase()).addClientKickedOutMessage(kicker, victim, reason);
channelHashMap.get(channel.toLowerCase()).removeUserFromListModel(victim);
match = true;
}
return match;
}
/**
* Command: NICK (3.1.2.)
* Kijken of de verzonden lijn een NICK bericht is.
* NICK command zorgt ervoor dat iemand een loginnaam kan krijgen of veranderen.
* De HashMap wordt doorlopen om alle kanalen te verwittigen dat de user zijn loginnaam heeft veranderd,
* enkel de kanalen waar de user zich bevindt gebruiken deze informatie.
* Het NICK bericht van JAVA1 om zijn naam te veranderen in JAVA2, is als volgt:
* :JAVA1!jto@tolsun.oulu.fi NICK JAVA2
* @param line De ontvangen lijn
* @return match True als boodschap een NICK is, anders false
*/
private boolean nickMessage(String line)
{
boolean match = false;
if (line.contains(" NICK ") && !line.contains("PRIVMSG"))
{
line = line.substring(1);
int indexOfExcl = line.indexOf("!");
String oldName = line.substring(0, indexOfExcl);
int indexOfColon = line.indexOf(":");
String newName = line.substring(indexOfColon+1);
System.out.println(oldName + " heeft zijn bijnaam veranderd naar " + newName);
Set<Entry<String, IRCChannel>> channelSet = channelHashMap.entrySet();
for(Entry<String, IRCChannel> entry : channelSet)
{
entry.getValue().addNickChangeMessage(oldName, newName);
entry.getValue().changeLoginName(oldName, newName);
}
match = true;
}
return match;
}
/**
* Command: QUIT (3.1.7.)
* Kijken of de verzonden lijn een QUIT bericht is.
* QUIT command geeft terug dat de client de chat verlaten heeft.
* Daarnaast wordt de hashMap doorlopen en worden alle kanalen verwittigt dat de client weg is.
* Enkel de kanalen waarin de client aanwezig was, gaat deze informatie gaan gebruiken.
* Het QUIT bericht dat toont dat JAVA1 de chat heeft verlaten met als reden "Ik ben weg.", is als volgt:
* :JAVA1!jto@tolsun.oulu.fi KICK :Ik ben weg.
* @param line De ontvangen lijn
* @return match True als boodschap een QUIT is, anders false
*/
private boolean quitMessage(String line)
{
boolean match = false;
if (line.contains(" QUIT ") && !line.contains("PRIVMSG"))
{
line = line.substring(1);
int indexOfExcl = line.indexOf("!");
String nickname = line.substring(0, indexOfExcl);
int indexOfColon = line.indexOf(":Quit:");
String reason = line.substring(indexOfColon+7);
Set<Entry<String, IRCChannel>> channelSet = channelHashMap.entrySet();
for(Entry<String, IRCChannel> entry : channelSet)
{
entry.getValue().addClientQuitServerMessage(nickname, reason);
entry.getValue().removeUserFromListModel(nickname);
}
match = true;
}
return match;
}
/**
* Command: Version (3.4.3.)
* Kijken of de verzonden lijn een VERSION bericht is.
* @param line De ontvangen lijn
* @return match True als boodschap een VERSION is, anders false
*/
private boolean VersionMessage(String line) throws IOException
{
boolean match = false;
line = line.substring(1);
final char SOH = 1; // ASCII character 0x01
if (line.substring(0,10).contains("!") && line.contains(SOH+"VERSION"+SOH))
{
int indexOfExcl = line.indexOf("!");
String nick = line.substring(0, indexOfExcl);
serverController.writeLine("NOTICE " + nick + " :" + SOH +"VERSION HoGent Datacom Client:1.0:Java SE 6.0" + SOH);
match = true;
}
return match;
}
/**
* Gaat na of de loginnaam geldig is (controleert op error replies), anders werpt hij een IllegalArgumentException.
* Error replies:
* 432 - ERR_ERRONEUSNICKNAME - Nadat een ongeldige nickname is doorgegeven.
* 433 - ERR_NICKNAMEINUSE - Nadat een bestaande nickname is doorgegeven.
* @param line De ontvangen lijn
* @return match Toont of de lijn de error reply 432 of 433 was of niet.
*/
private boolean loginError(String line) throws IllegalArgumentException
{
boolean match = false;
if ((line.contains("433") || line.contains("432")) && !line.contains("PRIVMSG"))
{
match = true;
throw new IllegalArgumentException("Je gekozen loginnaam is al in gebruik.\nProbeer opnieuw.");
}
return match;
}
/**
* Kijkt of de lijn een userlist is van een channel (volgens error reply 353):
* 353 - RPL_NAMREPLY
* "( "=" / "*" / "@" ) <channel>
* :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
* --> "@" secret channels
* --> "*" private channels
* --> "=" public channels
* @param line De ontvangen lijn
* @return match Toont of de lijn de error reply 353 was of niet.
*/
private boolean userListControl(String line)
{
boolean match = false;
if (line.contains(" 353 ") && !line.contains("PRIVMSG"))
{
line = line.substring(1);
int indexOfColon = line.indexOf(":");
int indexOfPound = line.indexOf("#");
String channel = line.substring(indexOfPound, indexOfColon-1);
String logins = line.substring(indexOfColon+1);
String[] arrayLogin;
String delimiter = " ";
arrayLogin = logins.split(delimiter); // Dit deelt de string (met de logins) op in een array.
channelHashMap.get(channel.toLowerCase()).addUsersToListModel(arrayLogin);
match = true;
}
return match;
}
/**
* Kijkt of de lijn een Motd message is, staat voor Message of the day.
* 376 - RPL_ENDOFMOTD
*
* @param line De ontvangen lijn
* @return match True als de lijn het einde van de MOTD aangeeft, anders false
*/
private boolean MotdMessage(String line) {
boolean match = false;
if (line.contains(" 376 ") && !line.contains("PRIVMSG")) {
setChanged();
notifyObservers();
match = true;
}
return match;
}
/**
* Controleert of we de server niet te vaak proberen aanroepen om te verbinden en dus overbelasten
*
* @param line
* @return
* @throws ThrottledException als je de server overbelast
*/
private boolean throttled(String line) throws ThrottledException
{
if (line.equals("ERROR :Your host is trying to (re)connect too fast -- throttled"))
{
throw new ThrottledException("Je probeert te snel na elkaar te verbinden.\nDe server laat dit niet toe.");
}
return false;
}
/**
* Exceptie die wordt gesmeten als je te vaak met dezelfde server probeert te verbinden op korte tijd.
*/
private class ThrottledException extends Exception
{
private static final long serialVersionUID = 3001089494818023780L;
public ThrottledException(String reason)
{
super(reason);
}
}
}