diff --git a/build.xml b/build.xml
index 6c9511a27..cb8413546 100644
--- a/build.xml
+++ b/build.xml
@@ -67,18 +67,23 @@
-
-
-
+
+
+
+
+
+
-
+
+
+
+
+
diff --git a/src/java/com/threerings/crowd/chat/server/ChatProvider.java b/src/java/com/threerings/crowd/chat/server/ChatProvider.java
index 04d9d4843..17ab51b93 100644
--- a/src/java/com/threerings/crowd/chat/server/ChatProvider.java
+++ b/src/java/com/threerings/crowd/chat/server/ChatProvider.java
@@ -49,8 +49,7 @@ import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.chat.data.UserMessage;
/**
- * The chat provider handles the server side of the chat-related
- * invocation services.
+ * The chat provider handles the server side of the chat-related invocation services.
*/
public class ChatProvider
implements ChatCodes, InvocationProvider
@@ -239,10 +238,10 @@ public class ChatProvider
*/
public void deliverTell (BodyObject target, UserMessage message)
{
- SpeakProvider.sendMessage(target, message);
+ SpeakUtil.sendMessage(target, message);
// note that the teller "heard" what they said
- SpeakProvider.noteMessage(message.speaker, message);
+ SpeakUtil.noteMessage(message.speaker, message);
}
/**
@@ -261,13 +260,13 @@ public class ChatProvider
{
if (from == null) {
if (attention) {
- SpeakProvider.sendAttention(object, bundle, msg);
+ SpeakUtil.sendAttention(object, bundle, msg);
} else {
- SpeakProvider.sendInfo(object, bundle, msg);
+ SpeakUtil.sendInfo(object, bundle, msg);
}
} else {
- SpeakProvider.sendSpeak(object, from, bundle, msg, BROADCAST_MODE);
+ SpeakUtil.sendSpeak(object, from, bundle, msg, BROADCAST_MODE);
}
}
diff --git a/src/java/com/threerings/crowd/chat/server/SpeakHandler.java b/src/java/com/threerings/crowd/chat/server/SpeakHandler.java
new file mode 100644
index 000000000..095194371
--- /dev/null
+++ b/src/java/com/threerings/crowd/chat/server/SpeakHandler.java
@@ -0,0 +1,104 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/narya/
+//
+// This library 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 2.1 of the License, or
+// (at your option) any later version.
+//
+// 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.crowd.chat.server;
+
+import com.threerings.util.MessageManager;
+
+import com.threerings.presents.data.ClientObject;
+import com.threerings.presents.dobj.DObject;
+import com.threerings.presents.server.InvocationManager;
+
+import com.threerings.crowd.Log;
+import com.threerings.crowd.chat.data.ChatCodes;
+import com.threerings.crowd.data.BodyObject;
+
+/**
+ * Wires up the {@link SpeakService} to a particular distributed object. A server entity can make
+ * "speech" available among the subscribers of a particular distributed object by constructing a
+ * speak handler and registering it with the {@link InvocationManager}, then placing the resulting
+ * marshaller into the distributed object in question so that subscribers to that object can use it
+ * to generate "speak" requests on that object.
+ */
+public class SpeakHandler
+ implements SpeakProvider
+{
+ /**
+ * Used to prevent abitrary users from issuing speak requests.
+ */
+ public static interface SpeakerValidator
+ {
+ /**
+ * Should return true if the supplied speaker is allowed to speak
+ * via the speak provider with which this validator was
+ * registered.
+ */
+ public boolean isValidSpeaker (DObject speakObj, ClientObject speaker, byte mode);
+ }
+
+ /**
+ * Creates a handler that will provide speech on the supplied distributed object.
+ *
+ * @param speakObj the object for which speech requests will be processed.
+ * @param validator an optional validator that can be used to prevent arbitrary users from
+ * using the speech services on this object.
+ */
+ public SpeakHandler (DObject speakObj, SpeakerValidator validator)
+ {
+ _speakObj = speakObj;
+ _validator = validator;
+ }
+
+ // from interface SpeakProvider
+ public void speak (ClientObject caller, String message, byte mode)
+ {
+ // ensure that the caller has normal chat privileges
+ BodyObject source = (BodyObject)caller;
+ String errmsg = source.checkAccess(ChatCodes.CHAT_ACCESS, null);
+ if (errmsg != null) {
+ // we normally don't listen for responses to speak messages so we can't just throw an
+ // InvocationException we have to specifically communicate the error to the user
+ SpeakUtil.sendFeedback(source, MessageManager.GLOBAL_BUNDLE, errmsg);
+ return;
+ }
+
+ // TODO: broadcast should be handled more like a system message rather than as a mode for a
+ // user message so that we don't have to do this validation here. Or not.
+
+ // ensure that the speaker is valid
+ if ((mode == ChatCodes.BROADCAST_MODE) ||
+ (_validator != null && !_validator.isValidSpeaker(_speakObj, caller, mode))) {
+ Log.warning("Refusing invalid speak request [caller=" + caller.who() +
+ ", speakObj=" + _speakObj.which() +
+ ", message=" + message + ", mode=" + mode + "].");
+
+ } else {
+ // issue the speak message on our speak object
+ SpeakUtil.sendSpeak(_speakObj, source.getVisibleName(), null, message, mode);
+ }
+ }
+
+ /** Our speech object. */
+ protected DObject _speakObj;
+
+ /** The entity that will validate our speakers. */
+ protected SpeakerValidator _validator;
+}
diff --git a/src/java/com/threerings/crowd/chat/server/SpeakProvider.java b/src/java/com/threerings/crowd/chat/server/SpeakProvider.java
index c3b967664..e1cce1695 100644
--- a/src/java/com/threerings/crowd/chat/server/SpeakProvider.java
+++ b/src/java/com/threerings/crowd/chat/server/SpeakProvider.java
@@ -21,417 +21,19 @@
package com.threerings.crowd.chat.server;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-import com.samskivert.util.ObserverList;
-import com.threerings.util.MessageManager;
-import com.threerings.util.Name;
-
+import com.threerings.crowd.chat.client.SpeakService;
+import com.threerings.presents.client.Client;
import com.threerings.presents.data.ClientObject;
-import com.threerings.presents.dobj.DObject;
+import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationProvider;
-import com.threerings.crowd.Log;
-import com.threerings.crowd.data.BodyObject;
-import com.threerings.crowd.server.CrowdServer;
-
-import com.threerings.crowd.chat.client.SpeakService;
-import com.threerings.crowd.chat.data.ChatCodes;
-import com.threerings.crowd.chat.data.ChatMessage;
-import com.threerings.crowd.chat.data.SpeakObject;
-import com.threerings.crowd.chat.data.SystemMessage;
-import com.threerings.crowd.chat.data.UserMessage;
-
/**
- * Provides the back-end of the chat speaking facilities. A server entity
- * can make "speech" available among the subscribers of a particular
- * distributed object by constructing a speak provider and registering it
- * with the invocation manager, then placing the resulting marshaller into
- * the distributed object in question so that subscribers to that object
- * can use it to generate "speak" requests on that object.
+ * Defines the server-side of the {@link SpeakService}.
*/
-public class SpeakProvider
- implements InvocationProvider, ChatCodes
+public interface SpeakProvider extends InvocationProvider
{
- /**
- * Used to prevent abitrary users from issuing speak requests.
- */
- public static interface SpeakerValidator
- {
- /**
- * Should return true if the supplied speaker is allowed to speak
- * via the speak provider with which this validator was
- * registered.
- */
- public boolean isValidSpeaker (DObject speakObj, ClientObject speaker, byte mode);
- }
-
- /**
- * An interface used to notify external systems whenever a chat
- * message is spoken by one user and heard by another.
- */
- public static interface MessageObserver
- {
- /**
- * Called for each player that hears a particular chat message.
- */
- public void messageDelivered (Name hearer, UserMessage message);
- }
-
- /**
- * Creates a speak provider that will provide speech on the supplied
- * distributed object.
- *
- * @param speakObj the object for which speech requests will be
- * processed.
- * @param validator an optional validator that can be used to prevent
- * arbitrary users from using the speech services on this object.
- */
- public SpeakProvider (DObject speakObj, SpeakerValidator validator)
- {
- _speakObj = speakObj;
- _validator = validator;
- }
-
- /**
- * Registers a {@link MessageObserver} to be notified whenever a
- * user-originated chat message is heard by another user.
- */
- public static void registerMessageObserver (MessageObserver obs)
- {
- _messageObs.add(obs);
- }
-
- /**
- * Removes a registration made previously with {@link
- * #registerMessageObserver}.
- */
- public static void removeMessageObserver (MessageObserver obs)
- {
- _messageObs.remove(obs);
- }
-
/**
* Handles a {@link SpeakService#speak} request.
*/
- public void speak (ClientObject caller, String message, byte mode)
- {
- // ensure that the caller has normal chat privileges
- BodyObject source = (BodyObject)caller;
- String errmsg = source.checkAccess(CHAT_ACCESS, null);
- if (errmsg != null) {
- // we normally don't listen for responses to speak messages so
- // we can't just throw an InvocationException we have to
- // specifically communicate the error to the user
- sendFeedback(source, MessageManager.GLOBAL_BUNDLE, errmsg);
- return;
- }
-
- // ensure that the speaker is valid
- // TODO: broadcast should be handled more like a system message
- // rather than as a mode for a user message so that we don't
- // have to do this validation here. Or not.
- if ((mode == BROADCAST_MODE) ||
- (_validator != null &&
- !_validator.isValidSpeaker(_speakObj, caller, mode))) {
- Log.warning("Refusing invalid speak request " +
- "[caller=" + caller.who() +
- ", speakObj=" + _speakObj.which() +
- ", message=" + message + ", mode=" + mode + "].");
-
- } else {
- // issue the speak message on our speak object
- sendSpeak(_speakObj, source.getVisibleName(), null, message, mode);
- }
- }
-
- /**
- * Sends a speak notification to the specified place object
- * originating with the specified speaker (the speaker optionally
- * being a server entity that wishes to fake a "speak" message) and
- * with the supplied message content.
- *
- * @param speakObj the object on which to generate the speak message.
- * @param speaker the username of the user that generated the message
- * (or some special speaker name for server messages).
- * @param bundle null when the message originates from a real human,
- * the bundle identifier that will be used by the client to translate
- * the message text when the message originates from a server entity
- * "faking" a chat message.
- * @param message the text of the speak message.
- */
- public static void sendSpeak (DObject speakObj, Name speaker,
- String bundle, String message)
- {
- sendSpeak(speakObj, speaker, bundle, message, ChatCodes.DEFAULT_MODE);
- }
-
- /**
- * Sends a speak notification to the specified place object
- * originating with the specified speaker (the speaker optionally
- * being a server entity that wishes to fake a "speak" message) and
- * with the supplied message content.
- *
- * @param speakObj the object on which to generate the speak message.
- * @param speaker the username of the user that generated the message
- * (or some special speaker name for server messages).
- * @param bundle null when the message originates from a real human,
- * the bundle identifier that will be used by the client to translate
- * the message text when the message originates from a server entity
- * "faking" a chat message.
- * @param message the text of the speak message.
- * @param mode the mode of the message, see {@link
- * ChatCodes#DEFAULT_MODE}.
- */
- public static void sendSpeak (DObject speakObj, Name speaker,
- String bundle, String message, byte mode)
- {
- sendMessage(speakObj, new UserMessage(speaker, bundle, message, mode));
- }
-
- /**
- * Sends a system INFO message notification to the specified object with
- * the supplied message content. A system message is one that will be
- * rendered where the speak messages are rendered, but in a way that
- * makes it clear that it is a message from the server.
- *
- * Info messages are sent when something happens that was neither
- * directly triggered by the user, nor requires direct action.
- *
- * @param speakObj the object on which to deliver the message.
- * @param bundle the name of the localization bundle that should be
- * used to translate this system message prior to displaying it to the
- * client.
- * @param message the text of the message.
- */
- public static void sendInfo (DObject speakObj, String bundle, String message)
- {
- sendSystem(speakObj, bundle, message, SystemMessage.INFO);
- }
-
- /**
- * Sends a system FEEDBACK message notification to the specified
- * object with the supplied message content. A system message is one
- * that will be rendered where the speak messages are rendered,
- * but in a way that makes it clear that it is a message from the server.
- *
- * Feedback messages are sent in direct response to a user action,
- * usually to indicate success or failure of the user's action.
- *
- * @param speakObj the object on which to deliver the message.
- * @param bundle the name of the localization bundle that should be
- * used to translate this system message prior to displaying it to the
- * client.
- * @param message the text of the message.
- */
- public static void sendFeedback (DObject speakObj, String bundle, String message)
- {
- sendSystem(speakObj, bundle, message, SystemMessage.FEEDBACK);
- }
-
- /**
- * Sends a system ATTENTION message notification to the specified
- * object with the supplied message content. A system message is one
- * that will be rendered where the speak messages are rendered,
- * but in a way that makes it clear that it is a message from the server.
- *
- * Attention messages are sent when something requires user action
- * that did not result from direct action by the user.
- *
- * @param speakObj the object on which to deliver the message.
- * @param bundle the name of the localization bundle that should be
- * used to translate this system message prior to displaying it to the
- * client.
- * @param message the text of the message.
- */
- public static void sendAttention (DObject speakObj, String bundle, String message)
- {
- sendSystem(speakObj, bundle, message, SystemMessage.ATTENTION);
- }
-
- /**
- * Send the specified system message on the specified dobj.
- */
- protected static void sendSystem (
- DObject speakObj, String bundle, String message, byte attLevel)
- {
- sendMessage(speakObj, new SystemMessage(message, bundle, attLevel));
- }
-
- /**
- * Send the specified message on the specified object.
- */
- public static void sendMessage (DObject speakObj, ChatMessage msg)
- {
- if (speakObj == null) {
- Log.warning("Dropping speak message, no speak obj '" + msg + "'.");
- Thread.dumpStack();
- return;
- }
-
- // post the message to the relevant object
- speakObj.postMessage(CHAT_NOTIFICATION, new Object[] { msg });
-
- // if this is a user message; add it to the heard history of all users that can "hear" it
- if (!(msg instanceof UserMessage)) {
- return;
- } else if (speakObj instanceof SpeakObject) {
- _messageMapper.message = (UserMessage)msg;
- ((SpeakObject)speakObj).applyToListeners(_messageMapper);
- _messageMapper.message = null;
- } else {
- Log.info("Unable to note listeners [dclass=" + speakObj.getClass() +
- ", msg=" + msg + "].");
- }
- }
-
- /**
- * Returns a list of {@link ChatMessage} objects to which this user
- * has been privy in the recent past.
- */
- public static ArrayList getChatHistory (Name username)
- {
- ArrayList history = getHistoryList(username);
- pruneHistory(System.currentTimeMillis(), history);
- return history;
- }
-
- /**
- * Called to clear the chat history for the specified user.
- */
- public static void clearHistory (Name username)
- {
- // Log.info("Clearing history for " + username + ".");
- _histories.remove(username);
- }
-
- /**
- * Notes that the specified user was privy to the specified message. If
- * {@link ChatMessage#timestamp} is not already filled in, it will be.
- */
- protected static void noteMessage (Name username, UserMessage msg)
- {
- // fill in the message's time stamp if necessary
- if (msg.timestamp == 0L) {
- msg.timestamp = System.currentTimeMillis();
- }
-
- recordToChatHistory(msg, username);
- // Log.info("Noted that " + username + " heard " + msg + ".");
-
- // notify any message observers
- _messageOp.init(username, msg);
- _messageObs.apply(_messageOp);
- }
-
- /**
- * Records the specified message to the specified users' chat histories.
- * If {@link ChatMessage#timestamp} is not already filled in, it will be.
- */
- public static void recordToChatHistory (UserMessage msg, Name... usernames)
- {
- // fill in the message's time stamp if necessary
- if (msg.timestamp == 0L) {
- msg.timestamp = System.currentTimeMillis();
- }
-
- for (Name username : usernames) {
- // add the message to this user's chat history
- ArrayList history = getHistoryList(username);
- history.add(msg);
-
- // if the history is big enough, potentially prune it (we always
- // prune when asked for the history, so this is just to balance
- // memory usage with CPU expense)
- if (history.size() > 15) {
- pruneHistory(msg.timestamp, history);
- }
- }
- }
-
- /**
- * Returns this user's chat history, creating one if necessary.
- */
- protected static ArrayList getHistoryList (Name username)
- {
- ArrayList history = _histories.get(username);
- if (history == null) {
- _histories.put(username, history = new ArrayList());
- }
- return history;
- }
-
- /**
- * Prunes all messages from this history which are expired.
- */
- protected static void pruneHistory (long now, ArrayList history)
- {
- int prunepos = 0;
- for (int ll = history.size(); prunepos < ll; prunepos++) {
- ChatMessage msg = history.get(prunepos);
- if (now - msg.timestamp < HISTORY_EXPIRATION) {
- break; // stop when we get to the first valid message
- }
- }
- history.subList(0, prunepos).clear();
- }
-
- /** Used to note the recipients of a chat message. */
- protected static class MessageMapper implements SpeakObject.ListenerOp
- {
- public UserMessage message;
-
- public void apply (int bodyOid) {
- DObject dobj = CrowdServer.omgr.getObject(bodyOid);
- if (dobj != null && dobj instanceof BodyObject) {
- noteMessage(((BodyObject)dobj).getVisibleName(), message);
- }
- }
-
- public void apply (Name username) {
- noteMessage(username, message);
- }
- }
-
- /** Used to notify our {@link MessageObserver}s. */
- protected static class MessageObserverOp
- implements ObserverList.ObserverOp
- {
- public void init (Name hearer, UserMessage message) {
- _hearer = hearer;
- _message = message;
- }
-
- public boolean apply (MessageObserver observer) {
- observer.messageDelivered(_hearer, _message);
- return true;
- }
-
- protected Name _hearer;
- protected UserMessage _message;
- }
-
- /** Our speech object. */
- protected DObject _speakObj;
-
- /** The entity that will validate our speakers. */
- protected SpeakerValidator _validator;
-
- /** Recent chat history for the server. */
- protected static HashMap> _histories =
- new HashMap>();
-
- /** Used to note the recipients of a chat message. */
- protected static MessageMapper _messageMapper = new MessageMapper();
-
- /** A list of {@link MessageObserver}s. */
- protected static ObserverList _messageObs =
- new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
-
- /** Used to notify our {@link MessageObserver}s. */
- protected static MessageObserverOp _messageOp = new MessageObserverOp();
-
- /** The amount of time before chat history becomes... history. */
- protected static final long HISTORY_EXPIRATION = 5L * 60L * 1000L;
+ public void speak (ClientObject caller, String arg1, byte arg2);
}
diff --git a/src/java/com/threerings/crowd/chat/server/SpeakUtil.java b/src/java/com/threerings/crowd/chat/server/SpeakUtil.java
new file mode 100644
index 000000000..518948ca6
--- /dev/null
+++ b/src/java/com/threerings/crowd/chat/server/SpeakUtil.java
@@ -0,0 +1,349 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/narya/
+//
+// This library 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 2.1 of the License, or
+// (at your option) any later version.
+//
+// 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.crowd.chat.server;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.samskivert.util.ObserverList;
+import com.threerings.util.Name;
+
+import com.threerings.presents.data.ClientObject;
+import com.threerings.presents.dobj.DObject;
+import com.threerings.presents.server.InvocationProvider;
+
+import com.threerings.crowd.Log;
+import com.threerings.crowd.data.BodyObject;
+import com.threerings.crowd.server.CrowdServer;
+
+import com.threerings.crowd.chat.client.SpeakService;
+import com.threerings.crowd.chat.data.ChatCodes;
+import com.threerings.crowd.chat.data.ChatMessage;
+import com.threerings.crowd.chat.data.SpeakObject;
+import com.threerings.crowd.chat.data.SystemMessage;
+import com.threerings.crowd.chat.data.UserMessage;
+
+/**
+ * Provides the back-end of the chat speaking facilities.
+ */
+public class SpeakUtil
+{
+ /**
+ * An interface used to notify external systems whenever a chat message is spoken by one user
+ * and heard by another.
+ */
+ public static interface MessageObserver
+ {
+ /**
+ * Called for each player that hears a particular chat message.
+ */
+ public void messageDelivered (Name hearer, UserMessage message);
+ }
+
+ /**
+ * Registers a {@link MessageObserver} to be notified whenever a user-originated chat message
+ * is heard by another user.
+ */
+ public static void registerMessageObserver (MessageObserver obs)
+ {
+ _messageObs.add(obs);
+ }
+
+ /**
+ * Removes a registration made previously with {@link #registerMessageObserver}.
+ */
+ public static void removeMessageObserver (MessageObserver obs)
+ {
+ _messageObs.remove(obs);
+ }
+
+ /**
+ * Sends a speak notification to the specified place object originating with the specified
+ * speaker (the speaker optionally being a server entity that wishes to fake a "speak" message)
+ * and with the supplied message content.
+ *
+ * @param speakObj the object on which to generate the speak message.
+ * @param speaker the username of the user that generated the message (or some special speaker
+ * name for server messages).
+ * @param bundle null when the message originates from a real human, the bundle identifier that
+ * will be used by the client to translate the message text when the message originates from a
+ * server entity "faking" a chat message.
+ * @param message the text of the speak message.
+ */
+ public static void sendSpeak (DObject speakObj, Name speaker, String bundle, String message)
+ {
+ sendSpeak(speakObj, speaker, bundle, message, ChatCodes.DEFAULT_MODE);
+ }
+
+ /**
+ * Sends a speak notification to the specified place object originating with the specified
+ * speaker (the speaker optionally being a server entity that wishes to fake a "speak" message)
+ * and with the supplied message content.
+ *
+ * @param speakObj the object on which to generate the speak message.
+ * @param speaker the username of the user that generated the message (or some special speaker
+ * name for server messages).
+ * @param bundle null when the message originates from a real human, the bundle identifier that
+ * will be used by the client to translate the message text when the message originates from a
+ * server entity "faking" a chat message.
+ * @param message the text of the speak message.
+ * @param mode the mode of the message, see {@link ChatCodes#DEFAULT_MODE}.
+ */
+ public static void sendSpeak (DObject speakObj, Name speaker, String bundle, String message,
+ byte mode)
+ {
+ sendMessage(speakObj, new UserMessage(speaker, bundle, message, mode));
+ }
+
+ /**
+ * Sends a system INFO message notification to the specified object with the supplied message
+ * content. A system message is one that will be rendered where the speak messages are
+ * rendered, but in a way that makes it clear that it is a message from the server.
+ *
+ * Info messages are sent when something happens that was neither directly triggered by the
+ * user, nor requires direct action.
+ *
+ * @param speakObj the object on which to deliver the message.
+ * @param bundle the name of the localization bundle that should be used to translate this
+ * system message prior to displaying it to the client.
+ * @param message the text of the message.
+ */
+ public static void sendInfo (DObject speakObj, String bundle, String message)
+ {
+ sendSystem(speakObj, bundle, message, SystemMessage.INFO);
+ }
+
+ /**
+ * Sends a system FEEDBACK message notification to the specified object with the supplied
+ * message content. A system message is one that will be rendered where the speak messages are
+ * rendered, but in a way that makes it clear that it is a message from the server.
+ *
+ * Feedback messages are sent in direct response to a user action, usually to indicate success
+ * or failure of the user's action.
+ *
+ * @param speakObj the object on which to deliver the message.
+ * @param bundle the name of the localization bundle that should be used to translate this
+ * system message prior to displaying it to the client.
+ * @param message the text of the message.
+ */
+ public static void sendFeedback (DObject speakObj, String bundle, String message)
+ {
+ sendSystem(speakObj, bundle, message, SystemMessage.FEEDBACK);
+ }
+
+ /**
+ * Sends a system ATTENTION message notification to the specified object with the supplied
+ * message content. A system message is one that will be rendered where the speak messages are
+ * rendered, but in a way that makes it clear that it is a message from the server.
+ *
+ * Attention messages are sent when something requires user action that did not result from
+ * direct action by the user.
+ *
+ * @param speakObj the object on which to deliver the message.
+ * @param bundle the name of the localization bundle that should be used to translate this
+ * system message prior to displaying it to the client.
+ * @param message the text of the message.
+ */
+ public static void sendAttention (DObject speakObj, String bundle, String message)
+ {
+ sendSystem(speakObj, bundle, message, SystemMessage.ATTENTION);
+ }
+
+ /**
+ * Send the specified message on the specified object.
+ */
+ public static void sendMessage (DObject speakObj, ChatMessage msg)
+ {
+ if (speakObj == null) {
+ Log.warning("Dropping speak message, no speak obj '" + msg + "'.");
+ Thread.dumpStack();
+ return;
+ }
+
+ // post the message to the relevant object
+ speakObj.postMessage(ChatCodes.CHAT_NOTIFICATION, new Object[] { msg });
+
+ // if this is a user message; add it to the heard history of all users that can "hear" it
+ if (!(msg instanceof UserMessage)) {
+ return;
+
+ } else if (speakObj instanceof SpeakObject) {
+ _messageMapper.message = (UserMessage)msg;
+ ((SpeakObject)speakObj).applyToListeners(_messageMapper);
+ _messageMapper.message = null;
+
+ } else {
+ Log.info("Unable to note listeners [dclass=" + speakObj.getClass() +
+ ", msg=" + msg + "].");
+ }
+ }
+
+ /**
+ * Returns a list of {@link ChatMessage} objects to which this user has been privy in the
+ * recent past.
+ */
+ public static ArrayList getChatHistory (Name username)
+ {
+ ArrayList history = getHistoryList(username);
+ pruneHistory(System.currentTimeMillis(), history);
+ return history;
+ }
+
+ /**
+ * Called to clear the chat history for the specified user.
+ */
+ public static void clearHistory (Name username)
+ {
+ // Log.info("Clearing history for " + username + ".");
+ _histories.remove(username);
+ }
+
+ /**
+ * Records the specified message to the specified users' chat histories. If {@link
+ * ChatMessage#timestamp} is not already filled in, it will be.
+ */
+ public static void recordToChatHistory (UserMessage msg, Name... usernames)
+ {
+ // fill in the message's time stamp if necessary
+ if (msg.timestamp == 0L) {
+ msg.timestamp = System.currentTimeMillis();
+ }
+
+ for (Name username : usernames) {
+ // add the message to this user's chat history
+ ArrayList history = getHistoryList(username);
+ history.add(msg);
+
+ // if the history is big enough, potentially prune it (we always prune when asked for
+ // the history, so this is just to balance memory usage with CPU expense)
+ if (history.size() > 15) {
+ pruneHistory(msg.timestamp, history);
+ }
+ }
+ }
+
+ /**
+ * Notes that the specified user was privy to the specified message. If {@link
+ * ChatMessage#timestamp} is not already filled in, it will be.
+ */
+ protected static void noteMessage (Name username, UserMessage msg)
+ {
+ // fill in the message's time stamp if necessary
+ if (msg.timestamp == 0L) {
+ msg.timestamp = System.currentTimeMillis();
+ }
+
+ recordToChatHistory(msg, username);
+ // Log.info("Noted that " + username + " heard " + msg + ".");
+
+ // notify any message observers
+ _messageOp.init(username, msg);
+ _messageObs.apply(_messageOp);
+ }
+
+ /**
+ * Send the specified system message on the specified dobj.
+ */
+ protected static void sendSystem (DObject speakObj, String bundle, String message, byte level)
+ {
+ sendMessage(speakObj, new SystemMessage(message, bundle, level));
+ }
+
+ /**
+ * Returns this user's chat history, creating one if necessary.
+ */
+ protected static ArrayList getHistoryList (Name username)
+ {
+ ArrayList history = _histories.get(username);
+ if (history == null) {
+ _histories.put(username, history = new ArrayList());
+ }
+ return history;
+ }
+
+ /**
+ * Prunes all messages from this history which are expired.
+ */
+ protected static void pruneHistory (long now, ArrayList history)
+ {
+ int prunepos = 0;
+ for (int ll = history.size(); prunepos < ll; prunepos++) {
+ ChatMessage msg = history.get(prunepos);
+ if (now - msg.timestamp < HISTORY_EXPIRATION) {
+ break; // stop when we get to the first valid message
+ }
+ }
+ history.subList(0, prunepos).clear();
+ }
+
+ /** Used to note the recipients of a chat message. */
+ protected static class MessageMapper implements SpeakObject.ListenerOp
+ {
+ public UserMessage message;
+
+ public void apply (int bodyOid) {
+ DObject dobj = CrowdServer.omgr.getObject(bodyOid);
+ if (dobj != null && dobj instanceof BodyObject) {
+ noteMessage(((BodyObject)dobj).getVisibleName(), message);
+ }
+ }
+
+ public void apply (Name username) {
+ noteMessage(username, message);
+ }
+ }
+
+ /** Used to notify our {@link MessageObserver}s. */
+ protected static class MessageObserverOp
+ implements ObserverList.ObserverOp
+ {
+ public void init (Name hearer, UserMessage message) {
+ _hearer = hearer;
+ _message = message;
+ }
+
+ public boolean apply (MessageObserver observer) {
+ observer.messageDelivered(_hearer, _message);
+ return true;
+ }
+
+ protected Name _hearer;
+ protected UserMessage _message;
+ }
+
+ /** Recent chat history for the server. */
+ protected static HashMap> _histories =
+ new HashMap>();
+
+ /** Used to note the recipients of a chat message. */
+ protected static MessageMapper _messageMapper = new MessageMapper();
+
+ /** A list of {@link MessageObserver}s. */
+ protected static ObserverList _messageObs =
+ new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
+
+ /** Used to notify our {@link MessageObserver}s. */
+ protected static MessageObserverOp _messageOp = new MessageObserverOp();
+
+ /** The amount of time before chat history becomes... history. */
+ protected static final long HISTORY_EXPIRATION = 5L * 60L * 1000L;
+}
diff --git a/src/java/com/threerings/crowd/server/CrowdClient.java b/src/java/com/threerings/crowd/server/CrowdClient.java
index 272006506..662964d9c 100644
--- a/src/java/com/threerings/crowd/server/CrowdClient.java
+++ b/src/java/com/threerings/crowd/server/CrowdClient.java
@@ -25,7 +25,7 @@ import com.threerings.presents.dobj.AccessController;
import com.threerings.presents.server.PresentsClient;
import com.threerings.crowd.Log;
-import com.threerings.crowd.chat.server.SpeakProvider;
+import com.threerings.crowd.chat.server.SpeakUtil;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.OccupantInfo;
import com.threerings.crowd.server.CrowdServer;
@@ -78,7 +78,7 @@ public class CrowdClient extends PresentsClient
// clear our chat history
if (body != null) {
- SpeakProvider.clearHistory(body.getVisibleName());
+ SpeakUtil.clearHistory(body.getVisibleName());
}
}
diff --git a/src/java/com/threerings/crowd/server/PlaceManager.java b/src/java/com/threerings/crowd/server/PlaceManager.java
index 7edf500e8..5d93e8f6d 100644
--- a/src/java/com/threerings/crowd/server/PlaceManager.java
+++ b/src/java/com/threerings/crowd/server/PlaceManager.java
@@ -58,7 +58,7 @@ import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.chat.data.SpeakMarshaller;
import com.threerings.crowd.chat.server.SpeakDispatcher;
-import com.threerings.crowd.chat.server.SpeakProvider;
+import com.threerings.crowd.chat.server.SpeakHandler;
/**
* The place manager is the server-side entity that handles all place-related interaction. It
@@ -76,7 +76,7 @@ import com.threerings.crowd.chat.server.SpeakProvider;
* well as through event listeners.
*/
public class PlaceManager
- implements MessageListener, OidListListener, ObjectDeathListener, SpeakProvider.SpeakerValidator
+ implements MessageListener, OidListListener, ObjectDeathListener, SpeakHandler.SpeakerValidator
{
/**
* An interface used to allow the registration of standard message handlers to be invoked by
@@ -231,7 +231,7 @@ public class PlaceManager
// to speak in this place
if (shouldCreateSpeakService()) {
plobj.setSpeakService((SpeakMarshaller)_invmgr.registerDispatcher(
- new SpeakDispatcher(new SpeakProvider(plobj, this))));
+ new SpeakDispatcher(new SpeakHandler(plobj, this))));
}
// we'll need to hear about place object events