diff --git a/src/java/com/threerings/crowd/Log.java b/src/java/com/threerings/crowd/Log.java index 29d02f271..5b21d130d 100644 --- a/src/java/com/threerings/crowd/Log.java +++ b/src/java/com/threerings/crowd/Log.java @@ -21,26 +21,22 @@ package com.threerings.crowd; +import java.util.logging.Level; +import java.util.logging.Logger; + /** - * A placeholder class that contains a reference to the log object used by - * the Crowd services. + * Contains a reference to the log object used by the Crowd services. */ public class Log { - public static com.samskivert.util.Log log = - new com.samskivert.util.Log("crowd"); - - /** Convenience function. */ - public static boolean debug () - { - return (com.samskivert.util.Log.getLevel() == - com.samskivert.util.Log.DEBUG); - } + /** We dispatch our log messages through this logger. */ + public static Logger log = + Logger.getLogger("com.threerings.narya.crowd"); /** Convenience function. */ public static void debug (String message) { - log.debug(message); + log.fine(message); } /** Convenience function. */ @@ -58,6 +54,6 @@ public class Log /** Convenience function. */ public static void logStackTrace (Throwable t) { - log.logStackTrace(com.samskivert.util.Log.WARNING, t); + log.log(Level.WARNING, t.getMessage(), t); } } diff --git a/src/java/com/threerings/crowd/data/PlaceObject.java b/src/java/com/threerings/crowd/data/PlaceObject.java index b7feefba2..98517abff 100644 --- a/src/java/com/threerings/crowd/data/PlaceObject.java +++ b/src/java/com/threerings/crowd/data/PlaceObject.java @@ -53,6 +53,22 @@ public class PlaceObject extends DObject public static final String SPEAK_SERVICE = "speakService"; // AUTO-GENERATED: FIELDS END + /** + * Exists solely to make calls into the manager look sensible: + *
_plobj.manager.invoke("someMethod", args);
+ */ + public class ManagerCaller + { + public void invoke (String method, Object ... args) { + postMessage(method, args); + } + } + + /** + * Allows the client to call methods on the manager. + */ + public transient ManagerCaller manager = new ManagerCaller(); + /** * Tracks the oid of the body objects of all of the occupants of this * place. diff --git a/src/java/com/threerings/crowd/server/PlaceManager.java b/src/java/com/threerings/crowd/server/PlaceManager.java index 108b474b8..dc34fad85 100644 --- a/src/java/com/threerings/crowd/server/PlaceManager.java +++ b/src/java/com/threerings/crowd/server/PlaceManager.java @@ -24,6 +24,7 @@ package com.threerings.crowd.server; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.logging.Level; import com.samskivert.util.HashIntMap; import com.samskivert.util.Interval; @@ -36,8 +37,7 @@ import com.threerings.presents.server.PresentsDObjectMgr; import com.threerings.presents.dobj.AccessController; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObjectManager; -import com.threerings.presents.dobj.EntryAddedEvent; -import com.threerings.presents.dobj.EntryRemovedEvent; +import com.threerings.presents.dobj.DynamicEventDispatcher; import com.threerings.presents.dobj.EntryUpdatedEvent; import com.threerings.presents.dobj.MessageEvent; import com.threerings.presents.dobj.MessageListener; @@ -46,7 +46,7 @@ import com.threerings.presents.dobj.ObjectDeathListener; import com.threerings.presents.dobj.ObjectDestroyedEvent; import com.threerings.presents.dobj.ObjectRemovedEvent; import com.threerings.presents.dobj.OidListListener; -import com.threerings.presents.dobj.SetListener; +import com.threerings.presents.dobj.SetAdapter; import com.threerings.crowd.Log; import com.threerings.crowd.data.BodyObject; @@ -79,12 +79,15 @@ import com.threerings.crowd.chat.server.SpeakProvider; */ public class PlaceManager implements MessageListener, OidListListener, ObjectDeathListener, - SetListener, SpeakProvider.SpeakerValidator + SpeakProvider.SpeakerValidator { /** - * An interface used to allow the registration of standard message - * handlers to be invoked by the place manager when particular types - * of message events are received. + * An interface used to allow the registration of standard message handlers + * to be invoked by the place manager when particular types of message + * events are received. + * + * @deprecated Use dynamically bound methods instead. See {@link + * DynamicEventDispatcher}. */ public static interface MessageHandler { @@ -160,27 +163,6 @@ public class PlaceManager _plobj.updateOccupantInfo((OccupantInfo)occInfo.clone()); } - /** - * Derived classes will generally override this method to create a custom - * {@link PlaceObject} derivation that contains extra information. - */ - protected PlaceObject createPlaceObject () - { - try { - return getPlaceObjectClass().newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * @deprecated Use {@link #createPlaceObject}. - */ - protected Class getPlaceObjectClass () - { - return PlaceObject.class; - } - /** * Called by the place registry after creating this place manager. */ @@ -241,15 +223,6 @@ public class PlaceManager return null; } - /** - * Called if the permissions check failed, to give place managers a - * chance to do any cleanup that might be necessary due to their early - * initialization or permissions checking code. - */ - protected void permissionsFailed () - { - } - /** * Called by the place manager after the place object has been * successfully created. @@ -268,6 +241,7 @@ public class PlaceManager // we'll need to hear about place object events plobj.addListener(this); + plobj.addListener(_bodyUpdater); // configure this place's access controller plobj.setAccessController(getAccessController()); @@ -279,40 +253,6 @@ public class PlaceManager checkShutdownInterval(); } - /** - * @return true if we should create a speaker service for our place object - * so that clients can use it to speak in this place. - */ - protected boolean shouldCreateSpeakService () - { - return true; - } - - /** - * Creates an access controller for this place's distributed object, which - * by default is {@link CrowdObjectAccess#PLACE}. - */ - protected AccessController getAccessController () - { - return CrowdObjectAccess.PLACE; - } - - /** - * Derived classes should override this (and be sure to call - * super.didStartup()) to perform any startup time - * initialization. The place object will be available by the time this - * method is executed. - */ - protected void didStartup () - { - // let our delegates know that we've started up - applyToDelegates(new DelegateOp() { - public void apply (PlaceManagerDelegate delegate) { - delegate.didStartup(_plobj); - } - }); - } - /** * Causes the place object being managed by this place manager to be * destroyed and the place manager to shut down. @@ -331,22 +271,6 @@ public class PlaceManager cancelShutdowner(); } - /** - * Called when this place has been destroyed and the place manager has - * shut down (via a call to {@link #shutdown}). Derived classes can - * override this method and perform any necessary shutdown time - * processing. - */ - protected void didShutdown () - { - // let our delegates know that we've shut down - applyToDelegates(new DelegateOp() { - public void apply (PlaceManagerDelegate delegate) { - delegate.didShutdown(); - } - }); - } - /** * Provides an opportunity for the place manager to prevent bodies from * entering. @@ -391,6 +315,87 @@ public class PlaceManager } } + /** + * Registers a particular message handler instance to be used when + * processing message events with the specified name. + * + * @param name the message name of the message events that should be + * handled by this handler. + * @param handler the handler to be registered. + * + * @deprecated Use dynamically bound methods instead. See {@link + * DynamicEventDispatcher}. + */ + public void registerMessageHandler (String name, MessageHandler handler) + { + // create our handler map if necessary + if (_msghandlers == null) { + _msghandlers = new HashMap(); + } + _msghandlers.put(name, handler); + } + + // from interface MessageListener + public void messageReceived (MessageEvent event) + { + MessageHandler handler = null; + if (_msghandlers != null) { + handler = _msghandlers.get(event.getName()); + } + if (handler != null) { + handler.handleEvent(event, this); + } + + // the first argument should be the client object of the caller or null + // if it is a server originated event + int srcoid = event.getSourceOid(); + DObject source = (srcoid <= 0) ? + null : CrowdServer.omgr.getObject(srcoid); + Object[] args = event.getArgs(), nargs; + if (args == null) { + nargs = new Object[] { source }; + } else { + nargs = new Object[args.length+1]; + nargs[0] = source; + System.arraycopy(args, 0, nargs, 1, args.length); + } + _dispatcher.dispatchMethod(event.getName(), nargs); + } + + // from interface OidListListener + public void objectAdded (ObjectAddedEvent event) + { + if (event.getName().equals(PlaceObject.OCCUPANTS)) { + bodyEntered(event.getOid()); + } + } + + // from interface OidListListener + public void objectRemoved (ObjectRemovedEvent event) + { + if (event.getName().equals(PlaceObject.OCCUPANTS)) { + bodyLeft(event.getOid()); + } + } + + // from interface ObjectDeathListener + public void objectDestroyed (ObjectDestroyedEvent event) + { + // unregister ourselves + _registry.unmapPlaceManager(this); + + // let our derived classes and delegates shut themselves down + didShutdown(); + } + + // documentation inherited from interface + public boolean isValidSpeaker ( + DObject speakObj, ClientObject speaker, byte mode) + { + // only allow people in the room to speak + return _plobj.occupants.contains(speaker.getOid()); + } + /** * Returns a string that can be used in log messages to identify the * place as sensibly as possible to the developer who has to puzzle @@ -404,6 +409,102 @@ public class PlaceManager (_plobj != null) ? String.valueOf(_plobj.getOid()) : "-1"); } + /** + * Generates a string representation of this manager. Does so in a way + * that makes it easier for derived classes to add to the string + * representation. + * + * @see #toString(StringBuilder) + */ + public String toString () + { + StringBuilder buf = new StringBuilder(); + buf.append("["); + toString(buf); + buf.append("]"); + return buf.toString(); + } + + /** + * Derived classes will generally override this method to create a custom + * {@link PlaceObject} derivation that contains extra information. + */ + protected PlaceObject createPlaceObject () + { + try { + return getPlaceObjectClass().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * @deprecated Use {@link #createPlaceObject}. + */ + protected Class getPlaceObjectClass () + { + return PlaceObject.class; + } + + /** + * Called if the permissions check failed, to give place managers a + * chance to do any cleanup that might be necessary due to their early + * initialization or permissions checking code. + */ + protected void permissionsFailed () + { + } + + /** + * @return true if we should create a speaker service for our place object + * so that clients can use it to speak in this place. + */ + protected boolean shouldCreateSpeakService () + { + return true; + } + + /** + * Creates an access controller for this place's distributed object, which + * by default is {@link CrowdObjectAccess#PLACE}. + */ + protected AccessController getAccessController () + { + return CrowdObjectAccess.PLACE; + } + + /** + * Derived classes should override this (and be sure to call + * super.didStartup()) to perform any startup time + * initialization. The place object will be available by the time this + * method is executed. + */ + protected void didStartup () + { + // let our delegates know that we've started up + applyToDelegates(new DelegateOp() { + public void apply (PlaceManagerDelegate delegate) { + delegate.didStartup(_plobj); + } + }); + } + + /** + * Called when this place has been destroyed and the place manager has + * shut down (via a call to {@link #shutdown}). Derived classes can + * override this method and perform any necessary shutdown time + * processing. + */ + protected void didShutdown () + { + // let our delegates know that we've shut down + applyToDelegates(new DelegateOp() { + public void apply (PlaceManagerDelegate delegate) { + delegate.didShutdown(); + } + }); + } + /** * Called when an occupant is being added to this place. This will be * before the call to {@link #bodyEntered} and gives the derived class a @@ -420,7 +521,7 @@ public class PlaceManager */ protected void bodyEntered (final int bodyOid) { - if (Log.debug()) { + if (Log.log.getLevel() == Level.FINE) { Log.debug("Body entered [where=" + where() + ", oid=" + bodyOid + "]."); } @@ -441,7 +542,7 @@ public class PlaceManager */ protected void bodyLeft (final int bodyOid) { - if (Log.debug()) { + if (Log.log.getLevel() == Level.FINE) { Log.debug("Body left [where=" + where() + ", oid=" + bodyOid + "]."); } @@ -551,122 +652,6 @@ public class PlaceManager return 5 * 60 * 1000L; } - /** - * Registers a particular message handler instance to be used when - * processing message events with the specified name. - * - * @param name the message name of the message events that should be - * handled by this handler. - * @param handler the handler to be registered. - */ - public void registerMessageHandler (String name, MessageHandler handler) - { - // create our handler map if necessary - if (_msghandlers == null) { - _msghandlers = new HashMap(); - } - _msghandlers.put(name, handler); - } - - /** - * Dispatches message events to registered message handlers. Derived - * classes should probably register message handlers rather than - * override this method directly. - */ - public void messageReceived (MessageEvent event) - { - MessageHandler handler = null; - if (_msghandlers != null) { - handler = _msghandlers.get(event.getName()); - } - if (handler != null) { - handler.handleEvent(event, this); - } - } - - /** - * Handles occupant arrival into the place. Derived classes may need - * to override this method to handle other oid lists in their derived - * place objects. They should be sure to call - * super.objectAdded if the event is one they don't - * explicitly handle. - */ - public void objectAdded (ObjectAddedEvent event) - { - if (event.getName().equals(PlaceObject.OCCUPANTS)) { - bodyEntered(event.getOid()); - } - } - - /** - * Handles occupant departure from the place. Derived classes may need - * to override this method to handle other oid lists in their derived - * place objects. They should be sure to call - * super.objectRemoved if the event is one they don't - * explicitly handle. - */ - public void objectRemoved (ObjectRemovedEvent event) - { - if (event.getName().equals(PlaceObject.OCCUPANTS)) { - bodyLeft(event.getOid()); - } - } - - /** - * Handles place destruction. We shut ourselves down and ask the place - * registry to unmap us. - */ - public void objectDestroyed (ObjectDestroyedEvent event) - { - // unregister ourselves - _registry.unmapPlaceManager(this); - - // let our derived classes and delegates shut themselves down - didShutdown(); - } - - // documentation inherited from interface - public void entryAdded (EntryAddedEvent event) - { - } - - // documentation inherited from interface - public void entryUpdated (EntryUpdatedEvent event) - { - if (event.getName().equals(PlaceObject.OCCUPANT_INFO)) { - bodyUpdated((OccupantInfo)event.getEntry()); - } - } - - // documentation inherited from interface - public void entryRemoved (EntryRemovedEvent event) - { - } - - // documentation inherited from interface - public boolean isValidSpeaker ( - DObject speakObj, ClientObject speaker, byte mode) - { - // only allow people in the room to speak - return _plobj.occupants.contains(speaker.getOid()); - } - - /** - * Generates a string representation of this manager. Does so in a way - * that makes it easier for derived classes to add to the string - * representation. - * - * @see #toString(StringBuilder) - */ - public String toString () - { - StringBuilder buf = new StringBuilder(); - buf.append("["); - toString(buf); - buf.append("]"); - return buf.toString(); - } - /** * An extensible way to add to the string representation of this * class. Override this (being sure to call super) and append your @@ -699,6 +684,15 @@ public class PlaceManager } } + /** Listens for occupant updates. */ + protected SetAdapter _bodyUpdater = new SetAdapter() { + public void entryUpdated (EntryUpdatedEvent event) { + if (event.getName().equals(PlaceObject.OCCUPANT_INFO)) { + bodyUpdated((OccupantInfo)event.getEntry()); + } + } + }; + /** A reference to the place registry with which we're registered. */ protected PlaceRegistry _registry; @@ -729,4 +723,9 @@ public class PlaceManager * down after a certain period of idility, or null if no * interval is currently registered. */ protected Interval _shutdownInterval; + + /** We use this to do our method lookup magic when we receive message + * events. */ + protected DynamicEventDispatcher _dispatcher = + new DynamicEventDispatcher(this); } diff --git a/src/java/com/threerings/presents/dobj/DynamicEventDispatcher.java b/src/java/com/threerings/presents/dobj/DynamicEventDispatcher.java new file mode 100644 index 000000000..5cf86db59 --- /dev/null +++ b/src/java/com/threerings/presents/dobj/DynamicEventDispatcher.java @@ -0,0 +1,135 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 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.presents.dobj; + +import java.lang.reflect.Method; + +import java.util.HashMap; +import java.util.logging.Level; + +import com.samskivert.util.MethodFinder; +import com.samskivert.util.StringUtil; + +import static com.threerings.presents.Log.log; + +/** + * Maps distributed object events to methods using reflection. + */ +public class DynamicEventDispatcher + implements AttributeChangeListener, ElementUpdateListener, SetListener +{ + /** + * Creates a dynamic event dispatcher on the supplied target. + */ + public DynamicEventDispatcher (Object target) + { + _target = target; + _finder = new MethodFinder(target.getClass()); + } + + // from interface AttributeChangeListener + public void attributeChanged (AttributeChangedEvent event) + { + dispatchMethod(event.getName() + "Changed", + new Object[] { event.getValue() }); + } + + // from interface ElementUpdateListener + public void elementUpdated (ElementUpdatedEvent event) + { + dispatchMethod(event.getName() + "Updated", + new Object[] { event.getIndex(), event.getValue() }); + } + + // from interface SetListener + public void entryAdded (EntryAddedEvent event) + { + dispatchMethod(event.getName() + "Added", + new Object[] { event.getEntry() }); + } + + // from interface SetListener + public void entryUpdated (EntryUpdatedEvent event) + { + dispatchMethod(event.getName() + "Updated", + new Object[] { event.getEntry() }); + } + + // from interface SetListener + public void entryRemoved (EntryRemovedEvent event) + { + dispatchMethod(event.getName() + "Removed", + new Object[] { event.getKey() }); + } + + /** + * Dynamically looks up the method in question on our target and dispatches + * an event if it does. + */ + public void dispatchMethod (String name, Object[] arguments) + { + // first check the cache + Method method = _mcache.get(name); + if (method == null) { + // if we haven't already determined this method doesn't exist, try + // to resolve it + if (!_mcache.containsKey(name)) { + _mcache.put(name, method = resolveMethod(name, arguments)); + } + } + if (method != null) { + try { + method.invoke(_target, arguments); + } catch (Exception e) { + log.log(Level.WARNING, "Failed to dispatch event callback " + + name + "(" + StringUtil.toString(arguments) + ").", e); + } + } + } + + /** + * Looks for a method that matches the supplied signature. + */ + protected Method resolveMethod (String name, Object[] arguments) + { + Class clazz = _target.getClass(); + Class[] ptypes = new Class[arguments.length]; + for (int ii = 0; ii < arguments.length; ii++) { + ptypes[ii] = arguments[ii] == null ? + Object.class : arguments[ii].getClass(); + } + try { + return _finder.findMethod(name, ptypes); + } catch (Exception e) { + return null; + } + } + + /** The object on which we will dynamically dispatch events. */ + protected Object _target; + + /** Used to look up methods. */ + protected MethodFinder _finder; + + /** A cache of already resolved methods. */ + protected HashMap _mcache = new HashMap(); +}