/*
* $Id$
*
* Copyright (c) 2000-2007 by Rodney Kinney
*
* 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.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
import VASSAL.tools.ArrayUtils;
import VASSAL.tools.PropertiesEncoder;
import VASSAL.tools.SequenceEncoder;
public class ServerNode extends Node {
private static final Logger logger = Logger.getLogger(ServerNode.class.getName());
private SendContentsTask sendContents;
public ServerNode() {
super(null, null, null);
sendContents = new SendContentsTask();
Timer t = new Timer();
t.schedule(sendContents, 0, 1000);
}
public synchronized void forward(String senderPath, String msg) {
MsgSender target = getMsgSender(senderPath);
target.send(msg);
}
public synchronized MsgSender getMsgSender(String path) {
Node[] target = new Node[]{this};
SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(path, '/');
while (st.hasMoreTokens()) {
String childId = st.nextToken();
if ("*".equals(childId)) { //$NON-NLS-1$
ArrayList<Node> l = new ArrayList<Node>();
for (Node node : target) {
l.addAll(Arrays.asList(node.getChildren()));
}
target = l.toArray(new Node[l.size()]);
}
else if (childId.startsWith("~")) { //$NON-NLS-1$
childId = childId.substring(1);
ArrayList<Node> l = new ArrayList<Node>();
for (Node node : target) {
for (Node child : node.getChildren()) {
if (!childId.equals(child.getId())) {
l.add(child);
}
}
}
target = l.toArray(new Node[l.size()]);
}
else {
int i = 0;
int n = target.length;
for (; i < n; ++i) {
Node child = target[i].getChild(childId);
if (child != null) {
target = new Node[]{child};
break;
}
}
if (i == n) {
target = new Node[0];
}
}
}
final MsgSender[] senders = ArrayUtils.copyOf(target);
return new MsgSender() {
public void send(String msg) {
for (int i = 0; i < senders.length; ++i) {
senders[i].send(msg);
}
}
};
}
public synchronized void disconnect(Node target) {
Node mod = getModule(target);
if (mod != null) {
Node room = target.getParent();
room.remove(target);
if (room.getChildren().length == 0) {
room.getParent().remove(room);
}
if (mod.getChildren().length == 0) {
remove(mod);
}
sendContents(mod);
}
}
protected synchronized void sendContents(Node module) {
sendContents.markChanged(module);
}
public synchronized void registerNode(String parentPath, Node newNode) {
Node newParent = Node.build(this, parentPath);
newParent.add(newNode);
Node module = getModule(newParent);
if (module != null) {
sendContents(module);
}
}
public Node getModule(Node n) {
Node module = n;
while (module != null && module.getParent() != this) {
module = module.getParent();
}
return module;
}
public synchronized void move(Node target, String newParentPath) {
Node oldMod = getModule(target);
Node newParent = Node.build(this, newParentPath);
newParent.add(target);
Node mod = getModule(newParent);
if (mod != null) {
sendContents(mod);
}
if (oldMod != mod && oldMod != null) {
sendContents(oldMod);
}
}
public synchronized void updateInfo(Node target) {
Node mod = getModule(target);
if (mod != null) {
sendContents(mod);
}
}
/**
* One client has requested to kick another out of a room. Validate that - Both players are in the same room - Kicker
* is the owner of the room
*
* @param kickerId
* Id of Kicking player
* @param kickeeId
* Id of Player to be kicked
*/
public synchronized void kick(PlayerNode kicker, String kickeeId) {
// Check the kicker owns the room he is in
final Node roomNode = kicker.getParent();
String roomOwnerId;
try {
roomOwnerId = new PropertiesEncoder(roomNode.getInfo()).getProperties().getProperty(NodeRoom.OWNER);
}
catch (IOException e) {
e.printStackTrace();
return;
}
if (roomOwnerId == null || !roomOwnerId.equals(kicker.getId())) {
return;
}
// Check the kickee belongs to the same room
final Node kickeeNode = roomNode.getChild(kickeeId);
if (kickeeNode == null) {
return;
}
// Kick to the default room and tell them they have been kicked
final Node defaultRoomNode = roomNode.getParent().getChildren()[0];
move(kickeeNode, defaultRoomNode.getPath());
}
private static class SendContentsTask extends TimerTask {
// FIXME: should modules be wrapped by Collections.synchronizedMap()?
private Set<Node> modules = new HashSet<Node>();
public void markChanged(Node module) {
synchronized (modules) {
modules.add(module);
}
}
public void run() {
HashSet<Node> s = new HashSet<Node>();
synchronized (modules) {
s.addAll(modules);
}
for (Node module : s) {
logger.fine("Sending contents of " + module.getId()); //$NON-NLS-1$
Node[] players = module.getLeafDescendants();
Node[] rooms = module.getChildren();
String listCommand = Protocol.encodeListCommand(players);
module.send(listCommand);
logger.finer(listCommand);
String roomInfo = Protocol.encodeRoomsInfo(rooms);
module.send(roomInfo);
logger.finer(roomInfo);
}
synchronized (modules) {
modules.clear();
}
}
}
}