VASSAL.chat.node.NodeClient.java Source code

Java tutorial

Introduction

Here is the source code for VASSAL.chat.node.NodeClient.java

Source

/*
 *
 * Copyright (c) 2000-2009 by Rodney Kinney, Brent Easton
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */
package VASSAL.chat.node;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.util.Properties;

import org.apache.commons.codec.binary.Base64;

import VASSAL.Info;
import VASSAL.build.GameModule;
import VASSAL.chat.CgiServerStatus;
import VASSAL.chat.Compressor;
import VASSAL.chat.InviteCommand;
import VASSAL.chat.InviteEncoder;
import VASSAL.chat.LockableChatServerConnection;
import VASSAL.chat.LockableRoom;
import VASSAL.chat.MainRoomChecker;
import VASSAL.chat.Player;
import VASSAL.chat.PlayerEncoder;
import VASSAL.chat.PrivateChatEncoder;
import VASSAL.chat.PrivateChatManager;
import VASSAL.chat.Room;
import VASSAL.chat.ServerStatus;
import VASSAL.chat.SimplePlayer;
import VASSAL.chat.SimpleRoom;
import VASSAL.chat.SimpleStatus;
import VASSAL.chat.SoundEncoder;
import VASSAL.chat.SynchEncoder;
import VASSAL.chat.WelcomeMessageServer;
import VASSAL.chat.messageboard.Message;
import VASSAL.chat.messageboard.MessageBoard;
import VASSAL.chat.ui.ChatControlsInitializer;
import VASSAL.chat.ui.ChatServerControls;
import VASSAL.chat.ui.InviteAction;
import VASSAL.chat.ui.KickAction;
import VASSAL.chat.ui.LockableRoomTreeRenderer;
import VASSAL.chat.ui.MessageBoardControlsInitializer;
import VASSAL.chat.ui.PrivateMessageAction;
import VASSAL.chat.ui.RoomInteractionControlsInitializer;
import VASSAL.chat.ui.SendSoundAction;
import VASSAL.chat.ui.ServerStatusControlsInitializer;
import VASSAL.chat.ui.ShowProfileAction;
import VASSAL.chat.ui.SimpleStatusControlsInitializer;
import VASSAL.chat.ui.SynchAction;
import VASSAL.command.Command;
import VASSAL.command.CommandEncoder;
import VASSAL.i18n.Resources;
import VASSAL.tools.ArrayUtils;
import VASSAL.tools.PropertiesEncoder;
import VASSAL.tools.SequenceEncoder;

/**
 * @author rkinney
 */
public abstract class NodeClient implements LockableChatServerConnection, PlayerEncoder, ChatControlsInitializer {
    public static final String ZIP_HEADER = "!ZIP!"; //$NON-NLS-1$
    protected PropertyChangeSupport propSupport = new PropertyChangeSupport(this);
    protected NodePlayer me;
    protected SimpleRoom currentRoom;
    protected String defaultRoomName = DEFAULT_ROOM_NAME; //$NON-NLS-1$
    protected NodeRoom[] allRooms = new NodeRoom[0];
    protected MessageBoard msgSvr;
    protected WelcomeMessageServer welcomer;
    protected ServerStatus serverStatus;
    protected String moduleName;
    protected String playerId;
    protected MainRoomChecker checker = new MainRoomChecker();
    protected int compressionLimit = 1000;
    protected CommandEncoder encoder;
    protected MessageBoardControlsInitializer messageBoardControls;
    protected RoomInteractionControlsInitializer roomControls;
    protected ServerStatusControlsInitializer serverStatusControls;
    protected SimpleStatusControlsInitializer playerStatusControls;
    protected SoundEncoder soundEncoder;
    protected PrivateChatEncoder privateChatEncoder;
    protected SynchEncoder synchEncoder;
    protected InviteEncoder inviteEncoder;
    protected PropertyChangeListener nameChangeListener;
    protected PropertyChangeListener profileChangeListener;
    protected NodeRoom pendingSynchToRoom;

    public NodeClient(String moduleName, String playerId, CommandEncoder encoder, MessageBoard msgSvr,
            WelcomeMessageServer welcomer) {
        this.encoder = encoder;
        this.msgSvr = msgSvr;
        this.welcomer = welcomer;
        this.playerId = playerId;
        this.moduleName = moduleName;
        serverStatus = new CgiServerStatus();
        me = new NodePlayer(playerId);
        messageBoardControls = new MessageBoardControlsInitializer(Resources.getString("Chat.messages"), msgSvr); //$NON-NLS-1$
        roomControls = new LockableNodeRoomControls(this);
        roomControls.addPlayerActionFactory(ShowProfileAction.factory());
        roomControls.addPlayerActionFactory(SynchAction.factory(this));
        PrivateChatManager privateChatManager = new PrivateChatManager(this);
        roomControls.addPlayerActionFactory(PrivateMessageAction.factory(this, privateChatManager));
        roomControls.addPlayerActionFactory(SendSoundAction.factory(this, Resources.getString("Chat.send_wakeup"), //$NON-NLS-1$
                "wakeUpSound", "phone1.wav")); //$NON-NLS-1$ //$NON-NLS-2$
        roomControls.addPlayerActionFactory(InviteAction.factory(this));
        roomControls.addPlayerActionFactory(KickAction.factory(this));
        serverStatusControls = new ServerStatusControlsInitializer(serverStatus);
        playerStatusControls = new SimpleStatusControlsInitializer(this);
        synchEncoder = new SynchEncoder(this, this);
        privateChatEncoder = new PrivateChatEncoder(this, privateChatManager);
        soundEncoder = new SoundEncoder(this);
        inviteEncoder = new InviteEncoder(this);
        nameChangeListener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                SimplePlayer p = (SimplePlayer) getUserInfo();
                p.setName((String) evt.getNewValue());
                setUserInfo(p);
            }
        };
        profileChangeListener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                SimplePlayer p = (SimplePlayer) getUserInfo();
                SimpleStatus s = (SimpleStatus) p.getStatus();
                s = new SimpleStatus(s.isLooking(), s.isAway(), (String) evt.getNewValue(), s.getClient(),
                        s.getIp(), s.getModuleVersion(), s.getCrc());
                p.setStatus(s);
                setUserInfo(p);
            }
        };
    }

    public void setConnected(boolean connect) {
        if (connect) {
            if (!isConnected()) {
                try {
                    NodePlayer oldPlayer = me;
                    me = new NodePlayer(playerId);
                    setUserInfo(oldPlayer);
                    initializeConnection();
                    Command welcomeMessage = welcomer.getWelcomeMessage();
                    if (welcomeMessage != null) {
                        welcomeMessage.execute();
                    }
                    registerNewConnection();
                }
                // FIXME: review error message
                catch (IOException e) {
                    propSupport.firePropertyChange(STATUS, null,
                            Resources.getString("Chat.unable_to_establish", e.getMessage())); //$NON-NLS-1$
                }
            }
        } else {
            if (isConnected()) {
                closeConnection();
            }
            currentRoom = null;
            allRooms = new NodeRoom[0];
        }
        propSupport.firePropertyChange(CONNECTED, null, isConnected() ? Boolean.TRUE : Boolean.FALSE);
    }

    protected void registerNewConnection() {
        String path = new SequenceEncoder(moduleName, '/').append(defaultRoomName).getValue();
        send(Protocol.encodeRegisterCommand(me.getId(), path,
                new PropertiesEncoder(me.toProperties()).getStringValue()));
        if (GameModule.getGameModule() != null) {
            String username = (String) GameModule.getGameModule().getPrefs().getValue("Login"); //$NON-NLS-1$
            if (username != null) {
                send(Protocol.encodeLoginCommand(username));
            }
        }
    }

    protected abstract void closeConnection();

    protected abstract void initializeConnection() throws IOException;

    public abstract void send(String command);

    public void setDefaultRoomName(String defaultRoomName) {
        this.defaultRoomName = defaultRoomName;
    }

    public String getDefaultRoomName() {
        return defaultRoomName;
    }

    public boolean isDefaultRoom(Room r) {
        return r == null ? false : r.getName().equals(getDefaultRoomName());
    }

    protected void sendStats() {
        if (isConnected()) {
            send(Protocol.encodeStatsCommand(new PropertiesEncoder(me.toProperties()).getStringValue()));
        }
    }

    public void sendToOthers(Command c) {
        sendToOthers(encoder.encode(c));
    }

    public void sendToAll(String msg) {
        if (currentRoom != null) {
            String path = new SequenceEncoder(moduleName, '/').append(currentRoom.getName()).getValue();
            forward(path, msg);
        }
    }

    public void forward(String receipientPath, String msg) {
        if (isConnected() && currentRoom != null && msg != null) {
            msg = checker.filter(msg, defaultRoomName, currentRoom.getName());
            if (msg.length() > compressionLimit) {
                try {
                    msg = ZIP_HEADER + Base64.encodeBase64String(Compressor.compress(msg.getBytes("UTF-8")));
                }
                // FIXME: review error message
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
            send(Protocol.encodeForwardCommand(receipientPath, msg));
        }
    }

    public void sendToOthers(String msg) {
        if (currentRoom != null) {
            String path = new SequenceEncoder(moduleName, '/').append(currentRoom.getName())
                    .append("~" + me.getId()).getValue(); //$NON-NLS-1$
            forward(path, msg);
        }
    }

    public void sendTo(Player recipient, Command c) {
        String path = new SequenceEncoder(moduleName, '/').append("*").append(((NodePlayer) recipient).getId()) //$NON-NLS-1$
                .getValue();
        forward(path, encoder.encode(c));
    }

    public void doKick(Player kickee) {
        send(Protocol.encodeKickCommand(kickee.getId()));
    }

    public boolean isKickable(Player kickee) {
        if (kickee != null) {
            final Room room = getRoom();
            // Is this a locked room?
            if (room instanceof LockableRoom && ((LockableRoom) room).isLocked()) {
                if (room instanceof NodeRoom) {
                    // Is the target player in the same room?
                    if (((NodeRoom) room).contains(kickee)) {
                        final String owner = ((NodeRoom) room).getOwner();
                        // Do I own this room and the target is not me?
                        if (owner != null && owner.equals(getUserInfo().getId()) && !owner.equals(kickee.getId())) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    public boolean isInvitable(Player invitee) {
        if (invitee != null) {
            final Room room = getRoom();
            if (room instanceof NodeRoom) {
                // Is the target player in a different room?
                if (!((NodeRoom) room).contains(invitee)) {
                    final String owner = ((NodeRoom) room).getOwner();
                    // Do I own this room and the target is not me?
                    if (owner != null && owner.equals(getUserInfo().getId()) && !owner.equals(invitee.getId())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Send Invitation to another player to join the current room
     *
     * @param invitee
     *          Player to invite
     */
    public void sendInvite(Player invitee) {
        sendTo(invitee, new InviteCommand(me.getName(), me.getId(), getRoom().getName()));
    }

    /**
     * Process an invitation request from a player to join a room
     *
     * @param player
     *          Inviting player name
     * @param room
     *          Inviting room
     */
    public void doInvite(String playerId, String roomName) {
        for (Room room : getAvailableRooms()) {
            if (room.getName().equals(roomName)) {
                if (room instanceof NodeRoom) {
                    final String owner = ((NodeRoom) room).getOwner();
                    if (owner != null && owner.equals(playerId)) {
                        setRoom(room, playerId);
                        return;
                    }
                }
            }
        }
    }

    public Room getRoom() {
        return currentRoom;
    }

    public Room[] getAvailableRooms() {
        return allRooms;
    }

    public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
        propSupport.addPropertyChangeListener(propertyName, l);
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propSupport.addPropertyChangeListener(l);
    }

    public Player getUserInfo() {
        return me;
    }

    public NodePlayer getMyInfo() {
        return me;
    }

    public void setUserInfo(Player p) {
        me.setName(p.getName());
        me.setStatus(p.getStatus());
        sendStats();
        propSupport.firePropertyChange(PLAYER_INFO, null, me);
    }

    public void lockRoom(LockableRoom r) {
        if (r instanceof NodeRoom) {
            final NodeRoom n = (NodeRoom) r;
            if (n.getOwner().equals(me.getId())) {
                n.toggleLock();
                sendRoomInfo(n);
                propSupport.firePropertyChange(AVAILABLE_ROOMS, null, allRooms);
            }
        }
    }

    public void sendRoomInfo(NodeRoom r) {
        Node dummy = new Node(null, r.getName(), new PropertiesEncoder(r.getInfo()).getStringValue());
        if (isConnected()) {
            String msg = Protocol.encodeRoomsInfo(new Node[] { dummy });
            send(msg);
        }
    }

    public void setRoom(Room r) {
        setRoom(r, null);
    }

    public void setRoom(Room r, String password) {
        if (isConnected()) {
            final String newRoom = r.getName();
            final String newPath = new SequenceEncoder(moduleName, '/').append(newRoom).getValue();
            String msg = Protocol.encodeJoinCommand(newPath, password);
            send(msg);
            // Request a synch if we are not the owner
            if (r instanceof NodeRoom) {
                final NodeRoom room = (NodeRoom) r;
                if (newRoom.equals(defaultRoomName)) {
                    GameModule.getGameModule().getGameState().setup(false);
                } else if (!room.isOwner(me)) {
                    // We are not actually recorded as being in the new room until we get
                    // an update back from
                    // the server. Record a Synch required to the new room.
                    pendingSynchToRoom = room;
                    GameModule.getGameModule().warn(Resources.getString("Chat.synchronize_pending"));
                }
            }
        }
    }

    /**
     * Process a message received from the server
     *
     * @param msg
     *          Encoded message
     */
    public void handleMessageFromServer(String msg) {
        Node n;
        Properties p;
        if ((n = Protocol.decodeListCommand(msg)) != null) {
            Node mod = n.getChild(moduleName);
            if (mod != null) {
                updateRooms(mod);
            }
            // Rooms have been updated with any new players (including us), so perform
            // a Synchronize
            // for a move to a new room if needed.
            if (pendingSynchToRoom != null) {
                new SynchAction(pendingSynchToRoom.getOwningPlayer(), this).actionPerformed(null);
                pendingSynchToRoom = null;
                GameModule.getGameModule().warn(Resources.getString("Chat.synchronize_complete"));
            }
        } else if ((p = Protocol.decodeRoomsInfo(msg)) != null) {
            for (int i = 0; i < allRooms.length; ++i) {
                String infoString = p.getProperty(allRooms[i].getName());
                if (infoString != null && infoString.length() > 0) {
                    try {
                        Properties info = new PropertiesEncoder(infoString).getProperties();
                        allRooms[i].setInfo(info);
                    }
                    // FIXME: review error message
                    catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            propSupport.firePropertyChange(ROOM, null, currentRoom);
            propSupport.firePropertyChange(AVAILABLE_ROOMS, null, allRooms);
        } else if (Protocol.decodeRegisterRequest(msg)) {
            registerNewConnection();
        } else {
            if (msg.startsWith(ZIP_HEADER)) {
                try {
                    msg = new String(Compressor.decompress(Base64.decodeBase64(msg.substring(ZIP_HEADER.length()))),
                            "UTF-8");
                }
                // FIXME: review error message
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
            propSupport.firePropertyChange(INCOMING_MSG, null, msg);
        }
    }

    protected void updateRooms(Node module) {
        Node[] roomNodes = module.getChildren();
        NodeRoom[] rooms = new NodeRoom[roomNodes.length];
        int defaultRoomIndex = -1;
        for (int i = 0; i < roomNodes.length; ++i) {
            Node[] playerNodes = roomNodes[i].getChildren();
            NodePlayer[] players = new NodePlayer[playerNodes.length];
            boolean containsMe = false;
            for (int j = 0; j < playerNodes.length; ++j) {
                players[j] = new NodePlayer(playerNodes[j].getId());
                if (players[j].equals(me)) {
                    containsMe = true;
                }
                try {
                    Properties p = new PropertiesEncoder(playerNodes[j].getInfo()).getProperties();
                    players[j].setInfo(p);
                    if (players[j].equals(me)) {
                        me.setInfo(p);
                    }
                }
                // FIXME: review error message
                catch (IOException e) {
                    e.printStackTrace();
                }
            }

            rooms[i] = new NodeRoom(roomNodes[i].getId(), players);

            // Lock room to start with. The ROOM_INFO message will unlock
            // any rooms that are not locked. Prevents unwanted clients from
            // connecting while room is in an undefined state.
            if (!rooms[i].getName().equals(defaultRoomName)) {
                rooms[i].lock();
            }

            try {
                if (roomNodes[i].getInfo() != null) {
                    rooms[i].setInfo(new PropertiesEncoder(roomNodes[i].getInfo()).getProperties());
                }
            }
            // FIXME: review error message
            catch (IOException e) {
                e.printStackTrace();
            }
            if (containsMe) {
                currentRoom = rooms[i];
            }
            if (defaultRoomName.equals(rooms[i].getName())) {
                defaultRoomIndex = i;
            }
        }
        if (defaultRoomIndex < 0) {
            allRooms = ArrayUtils.prepend(rooms, new NodeRoom(defaultRoomName));
        } else {
            allRooms = rooms;
            NodeRoom swap = allRooms[0];
            allRooms[0] = allRooms[defaultRoomIndex];
            allRooms[defaultRoomIndex] = swap;
        }
        // Do not fire a PropertyChange request, The server will be following
        // immediately
        // with a Room List refresh which can cause Icons to flash unexpectedly.
        // propSupport.firePropertyChange(ROOM, null, currentRoom);
        // propSupport.firePropertyChange(AVAILABLE_ROOMS, null, allRooms);
    }

    public MessageBoard getMessageServer() {
        return msgSvr;
    }

    public Message[] getMessages() {
        return msgSvr.getMessages();
    }

    public void postMessage(String msg) {
        msgSvr.postMessage(msg);
    }

    public Player stringToPlayer(String s) {
        NodePlayer p = null;
        try {
            PropertiesEncoder propEncoder = new PropertiesEncoder(s);
            p = new NodePlayer(null);
            p.setInfo(propEncoder.getProperties());
        }
        // FIXME: review error message
        catch (IOException e) {
            e.printStackTrace();
        }
        return p;
    }

    public String playerToString(Player p) {
        Properties props = ((NodePlayer) p).toProperties();
        return new PropertiesEncoder(props).getStringValue();
    }

    public void initializeControls(ChatServerControls controls) {
        playerStatusControls.initializeControls(controls);
        messageBoardControls.initializeControls(controls);
        roomControls.initializeControls(controls);
        serverStatusControls.initializeControls(controls);
        final GameModule g = GameModule.getGameModule();
        g.addCommandEncoder(synchEncoder);
        g.addCommandEncoder(privateChatEncoder);
        g.addCommandEncoder(soundEncoder);
        g.addCommandEncoder(inviteEncoder);
        me.setName((String) g.getPrefs().getValue(GameModule.REAL_NAME));
        g.getPrefs().getOption(GameModule.REAL_NAME).addPropertyChangeListener(nameChangeListener);
        SimpleStatus s = (SimpleStatus) me.getStatus();
        s = new SimpleStatus(s.isLooking(), s.isAway(), (String) g.getPrefs().getValue(GameModule.PERSONAL_INFO),
                Info.getVersion(), s.getIp(),
                g.getGameVersion() + ((g.getArchiveWriter() == null) ? "" : " (Editing)"),
                Long.toHexString(g.getCrc()));
        me.setStatus(s);
        g.getPrefs().getOption(GameModule.PERSONAL_INFO).addPropertyChangeListener(profileChangeListener);
        controls.getRoomTree().setCellRenderer(new LockableRoomTreeRenderer());
    }

    public void uninitializeControls(ChatServerControls controls) {
        messageBoardControls.uninitializeControls(controls);
        roomControls.uninitializeControls(controls);
        serverStatusControls.uninitializeControls(controls);
        playerStatusControls.uninitializeControls(controls);
        GameModule.getGameModule().removeCommandEncoder(synchEncoder);
        GameModule.getGameModule().removeCommandEncoder(privateChatEncoder);
        GameModule.getGameModule().removeCommandEncoder(soundEncoder);
        GameModule.getGameModule().removeCommandEncoder(inviteEncoder);
        GameModule.getGameModule().getPrefs().getOption(GameModule.REAL_NAME)
                .removePropertyChangeListener(nameChangeListener);
        GameModule.getGameModule().getPrefs().getOption(GameModule.PERSONAL_INFO)
                .removePropertyChangeListener(profileChangeListener);
    }
}