package jrummikub.control.network; import java.util.UUID; import javax.swing.SwingUtilities; import jrummikub.model.GameSettings; import jrummikub.util.Event; import jrummikub.util.Event1; import jrummikub.util.Event2; import jrummikub.util.GameData; import jrummikub.util.IEvent; import jrummikub.util.IEvent1; import jrummikub.util.IEvent2; import jrummikub.util.LoginData; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.PacketExtensionFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.DefaultPacketExtension; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.packet.XMPPError.Type; import org.jivesoftware.smack.util.Base64; import org.jivesoftware.smackx.muc.DiscussionHistory; import org.jivesoftware.smackx.muc.MultiUserChat; class ConnectionControl { private final static String ELEMENT_NAME = "rummikub"; private final static String NAMESPACE = "http://home.universe-factory.net/rummikub/"; private final LoginData loginData; private volatile Connection connection; private volatile MultiUserChat muc; private Event connectedEvent = new Event(); private Event connectionFailedEvent = new Event(); private Event1 gameOfferEvent = new Event1(); private Event1 gameWithdrawalEvent = new Event1(); private Event2 gameJoinEvent = new Event2(); private Event2 gameLeaveEvent = new Event2(); private Event2 gameJoinAckEvent = new Event2(); private volatile GameData offeredGame; ConnectionControl(LoginData loginData) { this.loginData = loginData; } String getNickname() { return muc.getNickname(); } void connect() { new Thread(new ConnectRunner()).start(); } void disconnect() { connectedEvent = new Event(); connectionFailedEvent = new Event(); new Thread(new DisconnectRunner()).start(); } IEvent getConnectedEvent() { return connectedEvent; } IEvent getConnectionFailedEvent() { return connectionFailedEvent; } IEvent1 getGameOfferEvent() { return gameOfferEvent; } IEvent1 getGameWithdrawalEvent() { return gameWithdrawalEvent; } IEvent2 getGameJoinEvent() { return gameJoinEvent; } IEvent2 getGameLeaveEvent() { return gameLeaveEvent; } IEvent2 getGameJoinAckEvent() { return gameJoinAckEvent; } void offerGame(GameData data) { offeredGame = data; sendGameOffer(); } void withdrawGame(UUID uuid) { offeredGame = null; new Thread(new SendGameWithdrawRunner(uuid)).start(); } void joinGame(UUID uuid) { new Thread(new SendGameJoinRunner(uuid)).start(); } void leaveGame(UUID uuid) { new Thread(new SendGameLeaveRunner(uuid)).start(); } void ackJoinGame(UUID uuid, String recipient, boolean ack) { new Thread(new SendGameJoinAckRunner(uuid, recipient, ack)).start(); } private void sendGameOffer() { new Thread(new SendGameOfferRunner(offeredGame)).start(); } private static void emitLater(final Event event) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { event.emit(); } }); } private Message createMessage(PacketExtension extension) { Message message = muc.createMessage(); message.addExtension(extension); return message; } private static DefaultPacketExtension createJRummikubExtension() { return new DefaultPacketExtension(ELEMENT_NAME, NAMESPACE); } private void processPacket(Packet packet) { DefaultPacketExtension extension = (DefaultPacketExtension) packet .getExtension(ELEMENT_NAME, NAMESPACE); if (((Message) packet).getType() == Message.Type.error) { System.err.println("Received error message from '" + packet.getFrom() + "'"); return; } String sender = packet.getFrom(); sender = sender.substring(sender.indexOf('/') + 1); String messageType = extension.getValue("messageType"); if (messageType.equals("game_offer")) { UUID uuid = UUID.fromString(extension.getValue("uuid")); GameSettings settings = (GameSettings) Base64.decodeToObject(extension .getValue("gameSettings")); GameData gameData = new GameData(uuid, settings, sender); gameOfferEvent.emit(gameData); } else if (messageType.equals("game_withdrawal")) { gameWithdrawalEvent.emit(UUID.fromString(extension.getValue("uuid"))); } else if (messageType.equals("game_request")) { if (offeredGame != null) { sendGameOffer(); } } else if (messageType.equals("game_join")) { gameJoinEvent.emit(UUID.fromString(extension.getValue("uuid")), sender); } else if (messageType.equals("game_leave")) { gameLeaveEvent.emit(UUID.fromString(extension.getValue("uuid")), sender); } else if (messageType.equals("game_join_ack")) { gameJoinAckEvent.emit(UUID.fromString(extension.getValue("uuid")), Boolean.valueOf(extension.getValue("ack"))); } else { System.err.println("Received unrecognized message of type '" + messageType + "'"); } } private Message createGameOfferMessage(GameData data) { DefaultPacketExtension extension = createJRummikubExtension(); extension.setValue("messageType", "game_offer"); extension.setValue("uuid", data.getGameID().toString()); extension.setValue("gameSettings", Base64.encodeObject(data.getGameSettings(), Base64.GZIP)); return createMessage(extension); } private Message createGameWithdrawMessage(UUID uuid) { DefaultPacketExtension extension = createJRummikubExtension(); extension.setValue("messageType", "game_withdrawal"); extension.setValue("uuid", uuid.toString()); return createMessage(extension); } private Message createGameJoinMessage(UUID uuid) { DefaultPacketExtension extension = createJRummikubExtension(); extension.setValue("messageType", "game_join"); extension.setValue("uuid", uuid.toString()); return createMessage(extension); } private Message createGameLeaveMessage(UUID uuid) { DefaultPacketExtension extension = createJRummikubExtension(); extension.setValue("messageType", "game_leave"); extension.setValue("uuid", uuid.toString()); return createMessage(extension); } private Message createGameAckMessage(UUID uuid, String recipient, boolean ack) { DefaultPacketExtension extension = createJRummikubExtension(); extension.setValue("messageType", "game_join_ack"); extension.setValue("uuid", uuid.toString()); extension.setValue("ack", Boolean.toString(ack)); Message message = createMessage(extension); message.setType(Message.Type.normal); message.setTo(muc.getRoom() + "/" + recipient); return message; } private Message createGameRequestMessage() { DefaultPacketExtension extension = createJRummikubExtension(); extension.setValue("messageType", "game_request"); return createMessage(extension); } private class ConnectRunner implements Runnable { @Override public void run() { synchronized (ConnectionControl.this) { connection = new XMPPConnection(loginData.getServerName()); connection.addPacketListener(new PacketListener() { @Override public void processPacket(final Packet packet) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { ConnectionControl.this.processPacket(packet); } }); } }, new AndFilter(new PacketTypeFilter(Message.class), new PacketExtensionFilter(ELEMENT_NAME, NAMESPACE))); try { doConnect(); } catch (XMPPException e) { connection.disconnect(); connection = null; // TODO Auto-generated catch block e.printStackTrace(); emitLater(connectionFailedEvent); } } } private void doConnect() throws XMPPException { connection.connect(); connection.login(loginData.getUserName(), loginData.getPassword(), "JRummikub"); muc = new MultiUserChat(connection, loginData.getChannelName()); DiscussionHistory history = new DiscussionHistory(); history.setMaxStanzas(0); String nickname = loginData.getUserName(); // Loop until a unused nickname is found while (true) { try { muc.join(nickname, null, history, 10000); break; // Join was successful, break the loop } catch (XMPPException e) { XMPPError error = e.getXMPPError(); if (error.getType() == Type.CANCEL && error.getCode() == 409) { // There was a conflict, try again with another // nickname nickname += "_"; continue; } else { // An unknown error has occurred, cancel connect throw e; } } } emitLater(connectedEvent); new Thread(new SendGameRequestRunner()).start(); } } private class SendGameOfferRunner implements Runnable { private GameData data; public SendGameOfferRunner(GameData data) { this.data = data; } @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.sendPacket(createGameOfferMessage(data)); } } } } private class SendGameWithdrawRunner implements Runnable { private UUID uuid; public SendGameWithdrawRunner(UUID uuid) { this.uuid = uuid; } @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.sendPacket(createGameWithdrawMessage(uuid)); } } } } private class SendGameRequestRunner implements Runnable { @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.sendPacket(createGameRequestMessage()); } } } } private class SendGameJoinRunner implements Runnable { private UUID uuid; public SendGameJoinRunner(UUID uuid) { this.uuid = uuid; } @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.sendPacket(createGameJoinMessage(uuid)); } } } } private class SendGameLeaveRunner implements Runnable { private UUID uuid; public SendGameLeaveRunner(UUID uuid) { this.uuid = uuid; } @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.sendPacket(createGameLeaveMessage(uuid)); } } } } private class SendGameJoinAckRunner implements Runnable { private UUID uuid; private String recipient; private boolean ack; public SendGameJoinAckRunner(UUID uuid, String recipient, boolean ack) { this.uuid = uuid; this.recipient = recipient; this.ack = ack; } @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.sendPacket(createGameAckMessage(uuid, recipient, ack)); } } } } private class DisconnectRunner implements Runnable { @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.disconnect(); connection = null; } } } } }