diff --git a/pom.xml b/pom.xml index 3d66f1f0d..65d82c900 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 org.sonatype.oss @@ -43,9 +45,9 @@ - com.samskivert - samskivert - 1.5 + com.threerings + ooo-util + 1.0-SNAPSHOT compile @@ -60,12 +62,6 @@ 1.0 compile - - com.google.guava - guava - 10.0 - compile - com.samskivert jmustache diff --git a/src/main/java/com/threerings/util/DefaultMap.java b/src/main/java/com/threerings/util/DefaultMap.java deleted file mode 100644 index ccde431cb..000000000 --- a/src/main/java/com/threerings/util/DefaultMap.java +++ /dev/null @@ -1,129 +0,0 @@ -// -// $Id$ -// -// Narya library - tools for developing networked games -// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved -// http://code.google.com/p/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.util; - -import java.lang.reflect.Constructor; - -import java.util.HashMap; -import java.util.Map; - -import com.google.common.base.Function; -import com.google.common.collect.ForwardingMap; -import com.google.common.collect.Maps; - -/** - * Provides a map implementation that automatically creates, and inserts into the map, a default - * value for any value retrieved via the {@link #fetch} method which has no entry for that key. - */ -public class DefaultMap extends ForwardingMap -{ - /** - * Creates a default map backed by a {@link HashMap} that creates instances of the supplied - * class (using its no-args constructor) as defaults. - */ - public static DefaultMap newInstanceHashMap (Class clazz) - { - return newInstanceMap(Maps.newHashMap(), clazz); - } - - /** - * Creates a default map backed by the supplied map that creates instances of the supplied - * class (using its no-args constructor) as defaults. - */ - public static DefaultMap newInstanceMap (Map delegate, Class clazz) - { - Function creator = newInstanceCreator(clazz); - return newMap(delegate, creator); - } - - /** - * Returns a Function that makes instances of the supplied class (using its no-args - * constructor) as default values. - */ - public static Function newInstanceCreator (Class clazz) { - - final Constructor ctor; - try { - ctor = clazz.getConstructor(); - } catch (NoSuchMethodException nsme) { - throw new IllegalArgumentException(clazz + " must have a no-args constructor."); - } - return new Function() { - public V apply (K key) { - try { - return ctor.newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }; - } - - /** - * Creates a default map backed by a {@link HashMap} using the supplied default creator. - */ - public static DefaultMap newHashMap (Function creator) - { - return newMap(Maps.newHashMap(), creator); - } - - /** - * Creates a default map backed by the supplied map using the supplied default creator. - */ - public static DefaultMap newMap (Map delegate, Function creator) - { - return new DefaultMap(delegate, creator); - } - - /** - * Looks up the supplied key in the map, returning the value to which it is mapped. If the key - * has no mapping, a default value will be obtained for the requested key and placed into the - * map. The current and subsequent lookups will return that value. - */ - public V fetch (K key) - { - V value = get(key); - // null is a valid value, so we check containsKey() before creating a default - if (value == null && !containsKey(key)) { - put(key, value = _creator.apply(key)); - } - return value; - } - - /** - * Creates a default map backed by the supplied map using the supplied default creator. - */ - protected DefaultMap (Map delegate, Function creator) - { - _delegate = delegate; - _creator = creator; - } - - @Override // from ForwardingMap - protected Map delegate() - { - return _delegate; - } - - protected final Map _delegate; - protected final Function _creator; -} diff --git a/src/main/java/com/threerings/util/MessageBundle.java b/src/main/java/com/threerings/util/MessageBundle.java deleted file mode 100644 index fe4492270..000000000 --- a/src/main/java/com/threerings/util/MessageBundle.java +++ /dev/null @@ -1,413 +0,0 @@ -// -// $Id$ -// -// Narya library - tools for developing networked games -// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved -// http://code.google.com/p/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.util; - -import java.text.MessageFormat; -import java.util.Collection; -import java.util.Enumeration; -import java.util.MissingResourceException; -import java.util.ResourceBundle; - -import com.samskivert.text.MessageUtil; -import com.samskivert.util.StringUtil; - -import static com.threerings.NaryaLog.log; - -/** - * A message bundle provides an easy mechanism by which to obtain translated message strings from - * a resource bundle. It uses the {@link MessageFormat} class to substitute arguments into the - * translation strings. Message bundles would generally be obtained via the {@link MessageManager}, - * but could be constructed individually if so desired. - */ -public class MessageBundle -{ - /** - * Call this to "taint" any string that has been entered by an entity outside the application - * so that the translation code knows not to attempt to translate this string when doing - * recursive translations (see {@link #xlate}). - */ - public static String taint (Object text) - { - return MessageUtil.taint(text); - } - - /** - * Composes a message key with a single argument. The message can subsequently be translated - * in a single call using {@link #xlate}. - */ - public static String compose (String key, Object arg) - { - return MessageUtil.compose(key, new Object[] { arg }); - } - - /** - * Composes a message key with an array of arguments. The message can subsequently be - * translated in a single call using {@link #xlate}. - */ - public static String compose (String key, Object... args) - { - return MessageUtil.compose(key, args); - } - - /** - * Composes a message key with an array of arguments. The message can subsequently be - * translated in a single call using {@link #xlate}. - */ - public static String compose (String key, String... args) - { - return MessageUtil.compose(key, args); - } - - /** - * A convenience method for calling {@link #compose(String,Object[])} with an array of - * arguments that will be automatically tainted (see {@link #taint}). - */ - public static String tcompose (String key, Object... args) - { - return MessageUtil.tcompose(key, args); - } - - /** - * Required for backwards compatibility. Alas. - */ - public static String tcompose (String key, Object arg) - { - return MessageUtil.tcompose(key, new Object[] { arg }); - } - - /** - * Required for backwards compatibility. Alas. - */ - public static String tcompose (String key, Object arg1, Object arg2) - { - return MessageUtil.tcompose(key, new Object[] { arg1, arg2 }); - } - - /** - * A convenience method for calling {@link #compose(String,String[])} with an array of - * arguments that will be automatically tainted (see {@link #taint}). - */ - public static String tcompose (String key, String... args) - { - return MessageUtil.tcompose(key, args); - } - - /** - * Returns a fully qualified message key which, when translated by some other bundle, will - * know to resolve and utilize the supplied bundle to translate this particular key. - */ - public static String qualify (String bundle, String key) - { - return MessageUtil.qualify(bundle, key); - } - - /** - * Returns the bundle name from a fully qualified message key. - * - * @see #qualify - */ - public static String getBundle (String qualifiedKey) - { - return MessageUtil.getBundle(qualifiedKey); - } - - /** - * Returns the unqualified portion of the key from a fully qualified message key. - * - * @see #qualify - */ - public static String getUnqualifiedKey (String qualifiedKey) - { - return MessageUtil.getUnqualifiedKey(qualifiedKey); - } - - /** - * Initializes the message bundle which will obtain localized messages from the supplied - * resource bundle. The path is provided purely for reporting purposes. - */ - public void init (MessageManager msgmgr, String path, - ResourceBundle bundle, MessageBundle parent) - { - _msgmgr = msgmgr; - _path = path; - _bundle = bundle; - _parent = parent; - } - - /** - * Obtains the translation for the specified message key. No arguments are substituted into - * the translated string. If a translation message does not exist for the specified key, an - * error is logged and the key itself is returned so that the caller need not worry about - * handling a null response. - */ - public String get (String key) - { - // if this string is tainted, we don't translate it, instead we - // simply remove the taint character and return it to the caller - if (MessageUtil.isTainted(key)) { - return MessageUtil.untaint(key); - } - - String msg = getResourceString(key); - return (msg != null) ? msg : key; - } - - /** - * Adds all messages whose key starts with the specified prefix to the supplied collection. - * - * @param includeParent if true, messages from our parent bundle (and its parent bundle, all - * the way up the chain will be included). - */ - public void getAll (String prefix, Collection messages, boolean includeParent) - { - Enumeration iter = _bundle.getKeys(); - while (iter.hasMoreElements()) { - String key = iter.nextElement(); - if (key.startsWith(prefix)) { - messages.add(get(key)); - } - } - if (includeParent && _parent != null) { - _parent.getAll(prefix, messages, includeParent); - } - } - - /** - * Adds all keys for messages whose key starts with the specified prefix to the supplied - * collection. - * - * @param includeParent if true, messages from our parent bundle (and its parent bundle, all - * the way up the chain will be included). - */ - public void getAllKeys (String prefix, Collection keys, boolean includeParent) - { - Enumeration iter = _bundle.getKeys(); - while (iter.hasMoreElements()) { - String key = iter.nextElement(); - if (key.startsWith(prefix)) { - keys.add(key); - } - } - if (includeParent && _parent != null) { - _parent.getAllKeys(prefix, keys, includeParent); - } - } - - /** - * Returns true if we have a translation mapping for the supplied key, false if not. - */ - public boolean exists (String key) - { - return getResourceString(key, false) != null; - } - - /** - * Get a String from the resource bundle, or null if there was an error. - */ - public String getResourceString (String key) - { - return getResourceString(key, true); - } - - /** - * Get a String from the resource bundle, or null if there was an error. - * - * @param key the resource key. - * @param reportMissing whether or not the method should log an error if the resource didn't - * exist. - */ - public String getResourceString (String key, boolean reportMissing) - { - try { - if (_bundle != null) { - return _bundle.getString(key); - } - } catch (MissingResourceException mre) { - // fall through and try the parent - } - - // if we have a parent, try getting the string from them - if (_parent != null) { - String value = _parent.getResourceString(key, false); - if (value != null) { - return value; - } - // if we didn't find it in our parent, we want to fall - // through and report missing appropriately - } - - if (reportMissing) { - log.warning("Missing translation message", "bundle", _path, "key", key, new Exception()); - } - - return null; - } - - /** - * Obtains the translation for the specified message key. The specified arguments are - * substituted into the translated string. - * - *

If the first argument in the array is an {@link Integer} object, a translation will be - * selected accounting for plurality in the following manner. Assume a message key of - * m.widgets, the following translations should be defined:

 m.widgets.0 =
-     * no widgets. m.widgets.1 = {0} widget. m.widgets.n = {0} widgets. 
- * - * The specified argument is substituted into the translated string as appropriate. Consider - * using: - * - *
 m.widgets.n = {0,number,integer} widgets. 
- * - * to obtain proper insertion of commas and dots as appropriate for the locale. - * - *

See {@link MessageFormat} for more information on how the substitution is performed. If - * a translation message does not exist for the specified key, an error is logged and the key - * itself (plus the arguments) is returned so that the caller need not worry about handling a - * null response. - */ - public String get (String key, Object... args) - { - // if this is a qualified key, we need to pass the buck to the - // appropriate message bundle - if (key.startsWith(MessageUtil.QUAL_PREFIX)) { - MessageBundle qbundle = _msgmgr.getBundle(getBundle(key)); - return qbundle.get(getUnqualifiedKey(key), args); - } - - // Select the proper suffix if our first argument can be coaxed into an integer - String suffix = getSuffix(args); - String msg = getResourceString(key + suffix, false); - - if (msg == null) { - // Playing with fire: This only works because it's the same "" reference we return - // from getSuffix() - // Don't try this at home. Keep out of reach of children. If swallowed, consult - // StringUtil.isBlank() - if (suffix != "") { - // Try the original key - msg = getResourceString(key, false); - } - - if (msg == null) { - log.warning("Missing translation message", "bundle", _path, "key", key, - new Exception()); - - // return something bogus - return (key + StringUtil.toString(args)); - } - } - - return MessageFormat.format(MessageUtil.escape(msg), args); - } - - /** - * Obtains the translation for the specified message key. The specified arguments are - * substituted into the translated string. - */ - public String get (String key, String... args) - { - return get(key, (Object[]) args); - } - - /** - * A helper function for {@link #get(String,Object[])} that allows us to automatically perform - * plurality processing if our first argument can be coaxed to an {@link Integer}. - */ - public String getSuffix (Object[] args) - { - if (args.length > 0 && args[0] != null) { - try { - int count = (args[0] instanceof Integer) ? (Integer)args[0] : - Integer.parseInt(args[0].toString()); - switch (count) { - case 0: return ".0"; - case 1: return ".1"; - default: return ".n"; - } - } catch (NumberFormatException e) { - // Fall out - } - } - return ""; - } - - /** - * Obtains the translation for the specified compound message key. A compound key contains the - * message key followed by a tab separated list of message arguments which will be substituted - * into the translation string. - * - *

See {@link MessageFormat} for more information on how the substitution is performed. If - * a translation message does not exist for the specified key, an error is logged and the key - * itself (plus the arguments) is returned so that the caller need not worry about handling a - * null response. - */ - public String xlate (String compoundKey) - { - // if this is a qualified key, we need to pass the buck to the appropriate message bundle; - // we have to do it here because we want the compound arguments of this key to be - // translated in the context of the containing message bundle qualification - if (compoundKey.startsWith(MessageUtil.QUAL_PREFIX)) { - MessageBundle qbundle = _msgmgr.getBundle(getBundle(compoundKey)); - return qbundle.xlate(getUnqualifiedKey(compoundKey)); - } - - // to be more efficient about creating unnecessary objects, we - // do some checking before splitting - int tidx = compoundKey.indexOf('|'); - if (tidx == -1) { - return get(compoundKey); - - } else { - String key = compoundKey.substring(0, tidx); - String argstr = compoundKey.substring(tidx+1); - String[] args = StringUtil.split(argstr, "|"); - // unescape and translate the arguments - for (int ii = 0; ii < args.length; ii++) { - // if the argument is tainted, do no further translation - // (it might contain |s or other fun stuff) - if (MessageUtil.isTainted(args[ii])) { - args[ii] = MessageUtil.unescape(MessageUtil.untaint(args[ii])); - } else { - args[ii] = xlate(MessageUtil.unescape(args[ii])); - } - } - return get(key, (Object[]) args); - } - } - - @Override - public String toString () - { - return "[bundle=" + _bundle + ", path=" + _path + "]"; - } - - /** The message manager via whom we'll resolve fully qualified translation strings. */ - protected MessageManager _msgmgr; - - /** The path that identifies the resource bundle we are using to obtain our messages. */ - protected String _path; - - /** The resource bundle from which we obtain our messages. */ - protected ResourceBundle _bundle; - - /** Our parent bundle if we're not the global bundle. */ - protected MessageBundle _parent; -} diff --git a/src/main/java/com/threerings/util/MessageManager.java b/src/main/java/com/threerings/util/MessageManager.java deleted file mode 100644 index f889627a1..000000000 --- a/src/main/java/com/threerings/util/MessageManager.java +++ /dev/null @@ -1,243 +0,0 @@ -// -// $Id$ -// -// Narya library - tools for developing networked games -// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved -// http://code.google.com/p/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.util; - -import java.util.HashMap; -import java.util.Locale; -import java.util.MissingResourceException; -import java.util.ResourceBundle; -import com.google.common.collect.Maps; - -import com.samskivert.util.StringUtil; - -import static com.threerings.NaryaLog.log; - -/** - * The message manager provides a thin wrapper around Java's built-in localization support, - * supporting a policy of dividing up localization resources into logical units, all of the - * translations for which are contained in a single messages file. - * - *

The message manager assumes that the locale remains constant for the duration of its - * operation. If the locale were to change during the operation of the client, a call to - * {@link #setLocale} should be made to inform the message manager of the new locale (which will - * clear the message bundle cache). - */ -public class MessageManager -{ - /** - * The name of the global resource bundle (which other bundles revert to if they can't locate - * a message within themselves). It must be named global.properties and live at - * the top of the bundle hierarchy. - */ - public static final String GLOBAL_BUNDLE = "global"; - - /** - * Constructs a message manager with the supplied resource prefix and the default locale. The - * prefix will be prepended to the path of all resource bundles prior to their resolution. For - * example, if a prefix of rsrc.messages was provided and a message bundle with - * the name game.chess was later requested, the message manager would attempt to - * load a resource bundle with the path rsrc.messages.game.chess and would - * eventually search for a file in the classpath with the path - * rsrc/messages/game/chess.properties. - * - *

See the documentation for {@link ResourceBundle#getBundle(String,Locale,ClassLoader)} - * for a more detailed explanation of how resource bundle paths are resolved. - */ - public MessageManager (String resourcePrefix) - { - // keep the prefix - _prefix = resourcePrefix; - - // use the default locale - _locale = Locale.getDefault(); - log.debug("Using locale: " + _locale + "."); - - // make sure the prefix ends with a dot - if (!_prefix.endsWith(".")) { - _prefix += "."; - } - - // load up the global bundle - _global = getBundle(GLOBAL_BUNDLE); - } - - /** - * Get the locale that is being used to translate messages. This may be useful if using - * standard translations, for example new SimpleDateFormat("EEEE", getLocale()) to get the - * name of a weekday that matches the language being used for all other client translations. - */ - public Locale getLocale () - { - return _locale; - } - - /** - * Sets the locale to the specified locale. Subsequent message bundles fetched via the message - * manager will use the new locale. The message bundle cache will also be cleared. - */ - public void setLocale (Locale locale) - { - setLocale(locale, false); - } - - /** - * Sets the locale to the specified locale. Subsequent message bundles fetched via the message - * manager will use the new locale. The message bundle cache will also be cleared. - * - * @param updateGlobal set to true if you want the global bundle reloaded in the new locale - */ - public void setLocale (Locale locale, boolean updateGlobal) - { - _locale = locale; - _cache.clear(); - - if (updateGlobal) { - _global = getBundle(GLOBAL_BUNDLE); - } - } - - /** - * Sets the appropriate resource prefix for where to find subsequent message bundles. - */ - public void setPrefix (String resourcePrefix) - { - _prefix = resourcePrefix; - - // Need to reget the global bundle at the new prefix location. - _global = getBundle(GLOBAL_BUNDLE); - } - - /** - * Allows a custom classloader to be configured for locating translation resources. - */ - public void setClassLoader (ClassLoader loader) - { - _loader = loader; - } - - /** - * Fetches the message bundle for the specified path. If no bundle can be located with the - * specified path, a special bundle is returned that returns the untranslated message - * identifiers instead of an associated translation. This is done so that error code to handle - * a failed bundle load need not be replicated wherever bundles are used. Instead an error - * will be logged and the requesting service can continue to function in an impaired state. - */ - public MessageBundle getBundle (String path) - { - // first look in the cache - MessageBundle bundle = _cache.get(path); - if (bundle != null) { - return bundle; - } - - // if it's not cached, we'll need to resolve it - ResourceBundle rbundle = loadBundle(_prefix + path); - - // if the resource bundle contains a special resource, we'll interpret that as a derivation - // of MessageBundle to instantiate for handling that class - MessageBundle customBundle = null; - if (rbundle != null) { - String mbclass = null; - try { - mbclass = rbundle.getString(MBUNDLE_CLASS_KEY).trim(); - if (!StringUtil.isBlank(mbclass)) { - customBundle = (MessageBundle)Class.forName(mbclass).newInstance(); - } - - } catch (MissingResourceException mre) { - // nothing to worry about - - } catch (Throwable t) { - log.warning("Failure instantiating custom message bundle", "mbclass", mbclass, - "error", t); - } - } - - // initialize our message bundle, cache it and return it (if we couldn't resolve the - // bundle, the message bundle will cope with its null resource bundle) - bundle = createBundle(path, rbundle, customBundle); - _cache.put(path, bundle); - return bundle; - } - - /** - * Returns the bundle to use for the given path and resource bundle. If customBundle is - * non-null, it's an instance of the bundle class specified by the bundle itself and should be - * used as part of the created bundle. - */ - protected MessageBundle createBundle (String path, ResourceBundle rbundle, - MessageBundle customBundle) - { - // if there was no custom class, or we failed to instantiate the custom class, use a - // standard message bundle - if (customBundle == null) { - customBundle = new MessageBundle(); - } - initBundle(customBundle, path, rbundle); - return customBundle; - } - - /** - * Initializes the given bundle with this manager and the given path and resource bundle. - */ - protected void initBundle (MessageBundle bundle, String path, ResourceBundle rbundle) - { - bundle.init(this, path, rbundle, _global); - } - - /** - * Loads a bundle from the given path, or returns null if it can't be found. - */ - protected ResourceBundle loadBundle (String path) - { - try { - if (_loader != null) { - return ResourceBundle.getBundle(path, _locale, _loader); - } - return ResourceBundle.getBundle(path, _locale); - } catch (MissingResourceException mre) { - log.warning("Unable to resolve resource bundle", "path", path, "locale", _locale, - mre); - return null; - } - } - - /** The prefix we prepend to resource paths prior to loading. */ - protected String _prefix; - - /** The locale for which we're obtaining message bundles. */ - protected Locale _locale; - - /** A custom class loader that we use to load resource bundles. */ - protected ClassLoader _loader; - - /** A cache of instantiated message bundles. */ - protected HashMap _cache = Maps.newHashMap(); - - /** Our top-level message bundle, from which others obtain messages if - * they can't find them within themselves. */ - protected MessageBundle _global; - - /** A key that can contain the classname of a custom message bundle - * class to be used to handle messages for a particular bundle. */ - protected static final String MBUNDLE_CLASS_KEY = "msgbundle_class"; -} diff --git a/src/main/java/com/threerings/util/MethodProfiler.java b/src/main/java/com/threerings/util/MethodProfiler.java deleted file mode 100644 index 3cda2accd..000000000 --- a/src/main/java/com/threerings/util/MethodProfiler.java +++ /dev/null @@ -1,344 +0,0 @@ -// -// $Id$ -// -// Narya library - tools for developing networked games -// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved -// http://code.google.com/p/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.util; - -import java.util.Map; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.collect.Maps; - -import com.samskivert.util.StringUtil; - -import static com.threerings.NaryaLog.log; - -/** - * Records the times that it takes to call methods. Allows one simultaneous method call per thread - * (no nested calls). Uses java's nano second timer. Results are logged by method name. - */ -public class MethodProfiler -{ - /** - * The results of sampling for a single method. - */ - public static class Result - { - /** - * Creates a new result with the given values. - */ - Result (int numSamples, double average, double stdDev) - { - this.numSamples = numSamples; - this.averageTime = average; - this.standardDeviation = stdDev; - } - - /** Number of method calls sampled. */ - public final int numSamples; - - /** Average time spent in the method. */ - public final double averageTime; - - /** Standard deviation from the average. */ - public final double standardDeviation; - - // from Object - @Override - public String toString () - { - return StringUtil.fieldsToString(this); - } - } - - /** - * Runs some very basic tests of the method profiler. - */ - public static void main (String args[]) - throws InterruptedException - { - int testNum = 0; - if (args.length > 0) { - testNum = Integer.parseInt(args[0]); - } - switch (testNum) { - case 0: // rum some rpc threads - MethodProfiler test = new MethodProfiler(); - Thread t1 = test.new TestThread("testm1", 100, 50); - Thread t2 = test.new TestThread("testm2", 100, 50); - t1.start(); - t2.start(); - t1.join(); - t2.join(); - for (Map.Entry method : test.getResults().entrySet()) { - log.info(method.getKey(), "result", method.getValue()); - } - break; - case 1: - simpleSampleTest("Single", 100); - break; - case 2: - simpleSampleTest("Triple", 100, 0, 200); - break; - case 3: - simpleSampleTest("Multi", 0, 25, 50, 100, 125, 150, 175, 200, 112.5, 112.5, 112.5); - break; - case 4: - MethodProfiler test4 = new MethodProfiler(); - test4.enter("L1a"); - test4.enter("L2a"); - test4.swap("L2b"); - test4.enter("L3a"); - test4.exit(null); - test4.exit(null); - test4.swap("L1b"); - test4.swap("L1c"); - test4.exit(null); - for (Map.Entry result : test4.getResults().entrySet()) { - log.info("Results", "name", result.getKey(), "value", result.getValue()); - } - break; - } - } - - /** - * Gets all method results so far. - */ - public Map getResults () - { - Map cmap = _profiles.asMap(); - Map results = Maps.newHashMapWithExpectedSize(cmap.size()); - for (Map.Entry entry : cmap.entrySet()) { - synchronized (entry.getValue()) { - results.put(entry.getKey(), toResult(entry.getValue())); - } - } - return results; - } - - /** - * Notes that the calling thread has entered the given method and records the time stamp. - */ - public void enter (String methodName) - { - Method method = _stack.get().push(); - method.name = methodName; - method.entryTime = System.nanoTime(); - } - - /** - * Notes that the calling thread has exited the given method and records the time delta since - * entry. The method parameter is not strictly necessary but allows some error checking. If not - * null, it must match the most recent value given to {@link #enter} for the calling thread. - */ - public void exit (String methodName) - { - long nanos = System.nanoTime(); - Method method = _stack.get().pop(); - if (method == null || (methodName != null && !methodName.equals(method.name))) { - // TODO: warn, but only once - return; - } - - long elapsed = nanos - method.entryTime; - recordTime(method.fullName(), (double)elapsed / 1000000); - method.name = null; - // Clear the ThreadLocal after we've profiled our whole stack to prevent our class loader - // from hanging around - if (_stack.get().size() == 0) { - _stack.remove(); - } - } - - /** - * Transition to a new method or segment. Exits the current one and enters the given one. - */ - public void swap (String methodName) - { - exit(null); - enter(methodName); - } - - /** - * Clears out the profile for the current thread, and invokes the exit of the top-level method. - * This allows callers to use one try... finally block in their top-level method without skewing - * the results for nested methods that may have thrown exceptions and/or not called - * {@link #exit}. - */ - public void exitAndClear (String methodName) - { - Stack stack = _stack.get(); - while (stack.size() > 1) { - stack.pop(); - } - if (stack.size() > 0) { - exit(methodName); - } - } - - /** - * Clears all recorded methods and times. - */ - public void reset () - { - _profiles.invalidateAll(); - } - - /** - * Adds a sample to our profile of the given method. - */ - protected void recordTime (String method, double elapsedMs) - { - RunningStats stats = _profiles.getUnchecked(method); - synchronized (stats) { - stats.addSample(elapsedMs); - } - } - - /** - * For testing, just calls the {@link #enter} and {@link #exit} methods at a fixed interval - * for a given number of times. - */ - protected class TestThread extends Thread - { - public TestThread (String method, int methodCount, long sleep) - { - _method = method; - _methodCount = methodCount; - _sleep = sleep; - } - - // from Runnable - @Override public void run () - { - try { - for (int ii = 0; ii < _methodCount; ++ii) { - MethodProfiler.this.enter(_method); - Thread.sleep(_sleep); - MethodProfiler.this.exit(_method); - } - } catch (InterruptedException ie) { - } - } - - protected int _methodCount; - protected String _method; - protected long _sleep; - } - - /** - * Describes what we know about an in-progress method call. - */ - protected static class Method - { - /** The name of the entered method. */ - public String name; - - /** The time the method was entered. */ - public long entryTime; - - /** The parent of the method. */ - public Method caller; - - /** - * Gets this method's name, prefixed with all parent method names separated by dots. - */ - public String fullName () - { - if (caller != null) { - return caller.fullName() + "." + name; - } - return name; - } - } - - /** - * Describes what we know about a nested set of in progress method calls. This is a fake stack - * that avoid reallocating entries, i.e. pop() == push() and push() == pop(). - */ - protected static class Stack - { - public Method push () - { - if (_size == _methods.length) { - Method []realloc = new Method[_size + 1]; - System.arraycopy(_methods, 0, realloc, 0, _size); - _methods = realloc; - _methods[_size] = new Method(); - } - _methods[_size].name = null; - _methods[_size].caller = _size > 0 ? _methods[_size - 1] : null; - return _methods[_size++]; - } - - public Method pop () - { - if (_size == 0) { - return null; - } - return _methods[--_size]; - } - - public int size () - { - return _size; - } - - protected int _size; - protected Method[] _methods = {new Method()}; - } - - /** - * Runs the method profile stat collection with the given samples and logs the result. - */ - protected static void simpleSampleTest (String name, double... samples) - { - RunningStats stats = new RunningStats(); - for (double sample : samples) { - stats.addSample(sample); - } - log.info(name, "results", toResult(stats)); - } - - /** - * Calculates the results of the the profile. - */ - protected static Result toResult (RunningStats stats) - { - return new Result(stats.getNumSamples(), stats.getMean(), stats.getStandardDeviation()); - } - - /** Set of active methods in the current thread. */ - protected ThreadLocal _stack = new ThreadLocal() { - @Override protected Stack initialValue () { - return new Stack(); - } - }; - - /** Stats by method name. */ - protected final Cache _profiles = CacheBuilder.newBuilder() - .build(new CacheLoader() { - public RunningStats load (String key) { - return new RunningStats(); - } - }); -} diff --git a/src/main/java/com/threerings/util/RunningStats.java b/src/main/java/com/threerings/util/RunningStats.java deleted file mode 100644 index 914613142..000000000 --- a/src/main/java/com/threerings/util/RunningStats.java +++ /dev/null @@ -1,94 +0,0 @@ -// -// $Id$ -// -// Narya library - tools for developing networked games -// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved -// http://code.google.com/p/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.util; - -/** - * Calculates live values for the mean, variance and standard deviation of a set of samples. - * Not thread safe! - */ -public class RunningStats -{ - /** - * Adds a new sample. - */ - public void addSample (double sample) - { - // From http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#On-line_algorithm - _numSamples++; - double deltaToOld = sample - _mean; - _mean += deltaToOld / _numSamples; - double deltaToNew = sample - _mean; - _varianceSum += deltaToOld * deltaToNew; - if (sample < _min) { - _min = sample; - } - if (sample > _max) { - _max = sample; - } - } - - /** - * Returns the minimum sample added or {@link Double#POSITIVE_INFINITY} if no samples have - * been added. - */ - public double getMin () - { - return _min; - } - - /** - * Returns the maximum sample added or {@link Double#NEGATIVE_INFINITY} if no samples have - * been added. - */ - public double getMax () - { - return _max; - } - - public double getVariance () - { - if (getNumSamples() == 0) { - return 0; - } - return _varianceSum / getNumSamples(); - } - - public int getNumSamples () - { - return _numSamples; - } - - public double getMean () - { - return _mean; - } - - public double getStandardDeviation () - { - return Math.sqrt(getVariance()); - } - - protected int _numSamples; - protected double _mean; - protected double _varianceSum; - protected double _max = Double.NEGATIVE_INFINITY, _min = Double.POSITIVE_INFINITY; -} \ No newline at end of file diff --git a/src/main/java/com/threerings/util/TimeUtil.java b/src/main/java/com/threerings/util/TimeUtil.java deleted file mode 100644 index e8447562b..000000000 --- a/src/main/java/com/threerings/util/TimeUtil.java +++ /dev/null @@ -1,186 +0,0 @@ -// -// $Id$ -// -// Narya library - tools for developing networked games -// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved -// http://code.google.com/p/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.util; - -import java.util.ArrayList; - -import com.google.common.collect.Lists; - -/** - * Utility for times. - */ -public class TimeUtil -{ - /** Time unit constant. */ - public static final byte MILLISECOND = 0; - - /** Time unit constant. */ - public static final byte SECOND = 1; - - /** Time unit constant. */ - public static final byte MINUTE = 2; - - /** Time unit constant. */ - public static final byte HOUR = 3; - - /** Time unit constant. */ - public static final byte DAY = 4; - - // TODO: Weeks?, months? - protected static final byte MAX_UNIT = DAY; - - /** - * Returns (in seconds) the time elapsed between the supplied start and end timestamps (which - * must be in milliseconds). Partial seconds are truncated, not rounded. - */ - public static int elapsedSeconds (long startStamp, long endStamp) - { - if (endStamp < startStamp) { - throw new IllegalArgumentException("End time must be after start time " + - "[start=" + startStamp + ", end=" + endStamp + "]"); - } - return (int)((endStamp - startStamp)/1000L); - } - - /** - * Get a translatable string specifying the magnitude of the specified duration. Results will - * be between "1 second" and "X hours", with all times rounded to the nearest unit. "0 units" - * will never be displayed, the minimum is 1. - */ - public static String getTimeOrderString (long duration, byte minUnit) - { - return getTimeOrderString(duration, minUnit, MAX_UNIT); - } - - /** - * Get a translatable string specifying the magnitude of the specified duration, with the units - * of time bounded between the minimum and maximum specified. "0 units" will never be returned, - * the minimum is 1. - */ - public static String getTimeOrderString (long duration, byte minUnit, byte maxUnit) - { - // enforce sanity - minUnit = (byte) Math.min(minUnit, maxUnit); - maxUnit = (byte) Math.min(maxUnit, MAX_UNIT); - - for (byte uu = MILLISECOND; uu <= MAX_UNIT; uu++) { - int quantity = getQuantityPerUnit(uu); - if ((minUnit <= uu) && (duration < quantity || maxUnit == uu)) { - duration = Math.max(1, duration); - return MessageBundle.tcompose(getTransKey(uu), String.valueOf(duration)); - } - duration = Math.round(duration / quantity); - } - - // will not happen, because eventually gg will be MAX_UNIT - Thread.dumpStack(); - return null; - } - - /** - * Get a translatable string specifying the duration, down to the minimum granularity. - */ - public static String getTimeString (long duration, byte minUnit) - { - return getTimeString(duration, minUnit, false); - } - - /** - * Get a translatable string specifying the duration, down to the minimum granularity. - * - * Normally rounds down to the nearest minUnit, but optionally rounds up. - */ - public static String getTimeString (long duration, byte minUnit, boolean roundUp) - { - // sanity - minUnit = (byte) Math.min(minUnit, MAX_UNIT); - duration = Math.abs(duration); - - if (roundUp) { - long quantity = 1; - for (byte uu = MILLISECOND; uu < MAX_UNIT - 1; uu++) { - quantity *= getQuantityPerUnit(uu); - } - - if (duration % quantity > 0) { - duration += quantity; - } - } - - ArrayList list = Lists.newArrayList(); - int parts = 0; // how many parts are in the translation string? - for (byte uu = MILLISECOND; uu <= MAX_UNIT; uu++) { - int quantity = getQuantityPerUnit(uu); - if (minUnit <= uu) { - long amt = duration % quantity; - if (amt != 0) { - list.add(MessageBundle.tcompose(getTransKey(uu), String.valueOf(amt))); - parts++; - } - } - duration /= quantity; - if (duration <= 0 && parts > 0) { - break; - } - } - - if (parts == 0) { - // Wow, we didn't get ANYTHING? Okay, I guess that means it's zero of our minUnit - return MessageBundle.tcompose(getTransKey(minUnit), 0); - - } else if (parts == 1) { - return list.get(0); - - } else { - return MessageBundle.compose("m.times_" + parts, list.toArray()); - } - } - - /** - * Internal method to get the quantity for the specified unit. (Not very OO) - */ - protected static int getQuantityPerUnit (byte unit) - { - switch (unit) { - case MILLISECOND: return 1000; - case SECOND: case MINUTE: return 60; - case HOUR: return 24; - case DAY: return Integer.MAX_VALUE; - default: return -1; - } - } - - /** - * Internal method to get the translation key for the specified unit. (Not very OO) - */ - protected static String getTransKey (byte unit) - { - switch (unit) { - case MILLISECOND: return "m.millisecond"; - case SECOND: return "m.second"; - case MINUTE: return "m.minute"; - case HOUR: return "m.hour"; - case DAY: return "m.day"; - default: return null; - } - } -}