commit 6ab42050543b089928b02c4ec2bb2d2248dc58ef Author: Michael Bayne Date: Wed Oct 5 09:55:15 2011 -0700 A place for OOO utilities. Presently we have some servlet bits from threerings, but shortly I'll be pulling most of Narya's util package herein. This is a public library, so we don't want hopelessly threerings-specific things in there (put those in the newly slimmed down threerings library). What we can do is rely on Guava, which is what keeps useful things out of samskivert and often pushes them into weird places like Narya. What we can also do is use ooo-util to export the latest version of samskivert and Guava to all of our other projects. A project that relies on the latest snapshot of ooo-util can expect to have the latest snapshot of samskivert and Guava, and when either samskivert or Guava ships a versioned release, we'll ship a versioned release of ooo-util so that projects can bump to that release to obtain the latest transitive depends. We should stronly resist the addition of additional depends to this library, unless they are indisputably useful to every single library and project in the whole wide world. Guava made that cut a long while back, and samskivert lingers with us like an embarrassing uncle, but I don't imagine another universally useful library is likely to come along in this decade. But if it does, we've got a place to put it. diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..58bf807 --- /dev/null +++ b/build.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/etc/bootstrap.xml b/etc/bootstrap.xml new file mode 100644 index 0000000..b24d388 --- /dev/null +++ b/etc/bootstrap.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f3de546 --- /dev/null +++ b/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + com.threerings + ooo-parent + 1.0 + + + ooo-util + jar + 1.0-SNAPSHOT + ooo-util + + + + com.samskivert + samskivert + 1.5 + + + com.google.guava + guava + 10.0 + + + javax.servlet + servlet-api + 2.5 + provided + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + http://samskivert.googlecode.com/svn/apidocs/ + http://docs.guava-libraries.googlecode.com/git/javadoc/ + + + + + + diff --git a/src/main/java/com/threerings/Log.java b/src/main/java/com/threerings/Log.java new file mode 100644 index 0000000..4b26b26 --- /dev/null +++ b/src/main/java/com/threerings/Log.java @@ -0,0 +1,15 @@ +// +// $Id$ + +package com.threerings; + +import com.samskivert.util.Logger; + +/** + * Contains a reference to the log object used by this library. + */ +public class Log +{ + /** We dispatch our log messages through this logger. */ + public static Logger log = Logger.getLogger("ooo-util"); +} diff --git a/src/main/java/com/threerings/servlet/util/ConversionFailedException.java b/src/main/java/com/threerings/servlet/util/ConversionFailedException.java new file mode 100644 index 0000000..e9a5e9b --- /dev/null +++ b/src/main/java/com/threerings/servlet/util/ConversionFailedException.java @@ -0,0 +1,53 @@ +// +// $Id$ + +package com.threerings.servlet.util; + +import com.samskivert.util.LogBuilder; + +/** + * Indicates that a converter couldn't handle a parameter value in {@Parameters}. + */ +public class ConversionFailedException extends RuntimeException +{ + public ConversionFailedException (Throwable cause) + { + this(cause, ""); + } + + /** + * Creates an exception message with the given base message and key value pairs as formatted + * by {@link LogBuilder}. + */ + public ConversionFailedException (Throwable cause, Object msg, Object...args) + { + this(msg, args); + initCause(cause); + } + + public ConversionFailedException (Object msg, Object...args) + { + _builder = new LogBuilder(msg, args); + } + + public ConversionFailedException () + { + this(""); + } + + /** + * Adds the given key value pairs to the message. + */ + public void append (Object... args) + { + _builder.append(args); + } + + @Override + public String getMessage () + { + return _builder.toString(); + } + + protected final LogBuilder _builder; +} diff --git a/src/main/java/com/threerings/servlet/util/Converters.java b/src/main/java/com/threerings/servlet/util/Converters.java new file mode 100644 index 0000000..ff19269 --- /dev/null +++ b/src/main/java/com/threerings/servlet/util/Converters.java @@ -0,0 +1,39 @@ +// +// $Id$ + +package com.threerings.servlet.util; + +import com.google.common.base.Function; + +public class Converters +{ + public static final Function TO_LONG = new Function() { + public Long apply (String from) { + return Long.parseLong(from); + } + }; + + public static final Function TO_BOOLEAN = new Function() { + public Boolean apply (String from) { + return Boolean.parseBoolean(from); + } + }; + + public static final Function TO_INT = new Function() { + public Integer apply (String from) { + return Integer.parseInt(from); + } + }; + + public static final Function TO_BYTE = new Function() { + public Byte apply (String from) { + return Byte.parseByte(from); + } + }; + + public static final Function TO_FLOAT = new Function() { + public Float apply (String from) { + return Float.parseFloat(from); + } + }; +} diff --git a/src/main/java/com/threerings/servlet/util/FlashVarBuilder.java b/src/main/java/com/threerings/servlet/util/FlashVarBuilder.java new file mode 100644 index 0000000..976ec1b --- /dev/null +++ b/src/main/java/com/threerings/servlet/util/FlashVarBuilder.java @@ -0,0 +1,52 @@ +// +// $Id$ + +package com.threerings.servlet.util; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import com.google.common.collect.Maps; + +import com.samskivert.util.Tuple; + +import static com.threerings.Log.log; + +public class FlashVarBuilder +{ + /** + * Adds the given value to the parameter mapping under key replacing any current value. + * {@link String#valueOf} is called on val to turn it into a string. + */ + public FlashVarBuilder add (String key, Object val) + { + _params.put(key, String.valueOf(val)); + return this; + } + + /** + * Adds all the parameters in req to the parameters being constructed. If there's + * already a mapping for a given parameter name, the original value is left in place and a + * warning is printed. + */ + public FlashVarBuilder addAll (HttpServletRequest req) + { + for (Tuple entry : new Parameters(req).entries()) { + if (_params.containsKey(entry.left)) { + log.warning("Request contained an already defined value", "name", entry.left); + } else { + _params.put(entry.left, entry.right); + } + } + return this; + } + + @Override + public String toString () + { + return QueryBuilder.encode(new StringBuilder(), _params.entrySet()); + } + + protected final Map _params = Maps.newHashMap(); +} diff --git a/src/main/java/com/threerings/servlet/util/Parameters.java b/src/main/java/com/threerings/servlet/util/Parameters.java new file mode 100644 index 0000000..a935cb9 --- /dev/null +++ b/src/main/java/com/threerings/servlet/util/Parameters.java @@ -0,0 +1,205 @@ +// +// $Id$ + +package com.threerings.servlet.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import javax.servlet.ServletRequest; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +import com.samskivert.util.Logger; +import com.samskivert.util.Tuple; + +/** + * Provides convenient access to and conversion of the parameters of an HttpServletRequest. + */ +public class Parameters +{ + public Parameters (ServletRequest req) + { + _req = req; + } + + /** + * Returns an Iterable over all the parameter names for this request. + */ + public Set names () + { + @SuppressWarnings("unchecked") + Set names = _req.getParameterMap().keySet(); + return names; + } + + /** + * Returns a Collection of all the parameter values for this request. + */ + public Collection values () + { + @SuppressWarnings("unchecked") + Collection vals = _req.getParameterMap().values(); + return vals; + } + + /** + * Returns all the names and values in the parameters. The name is {@link Tuple#left} and the + * value is {@link Tuple#right}. If a name appears multiple times, it'll be in multiple + * tuples. + */ + public Collection> entries () + { + Set names = names(); + List> entries = Lists.newArrayListWithCapacity(names.size()); + for (String name : names) { + for (String val : getAll(name)) { + entries.add(Tuple.newTuple(name, val)); + } + } + return entries; + } + + /** + * Returns true if the request contains the given parameter. + */ + public boolean has (String name) + { + return _req.getParameterValues(name) != null; + } + + /** + * Returns the value for the parameter on this request or null if it has no such parameter. + * IllegalStateException is thrown if the request has multiple values for the parameter. + */ + public String get (String name) + { + return get(name, (String)null); + } + + /** + * Returns the value for the parameter on this request or def if it has no such + * parameter. IllegalStateException is thrown if the request has multiple values for the + * parameter. + */ + public String get (String name, String def) + { + String[] vals = _req.getParameterValues(name); + if (vals != null) { + Preconditions.checkState(vals.length == 1, + Logger.format("Requested a single value parameter, but there were multiple values", + "name", name, "values", vals)); + return vals[0]; + } + return def; + } + + /** + * Returns the value for the parameter on this request as converted by converter + * or defif it has no such parameter.

+ * + * If converter can't handle the value and throws + * ConversionFailedException, that will be propagated by this method with the + * parameter name and value filled in. Any other exceptions thrown by converter + * will be wrapped in ConversionFailedException and rethrown.

+ * + * IllegalStateException is thrown if the request has multiple values for the parameter. + */ + public T get (String name, Function converter, T def) + { + String val = get(name); + if (val != null) { + return convert(converter, name, val); + } + return def; + } + + /** + * Returns the value for the parameter on this request or throws + * NullPointerException if it has no such parameter. IllegalStateException is + * thrown if the request has multiple values for the parameter. + */ + public String require (String name) + { + String val = get(name); + Preconditions.checkNotNull(val, "'" + name + "' was required but not present"); + return val; + } + + /** + * Returns the value for the parameter on this request as converted by converter + * or throws NullPointerException if it has no such parameter.

+ * + * If converter can't handle the value and throws + * ConversionFailedException, that will be propagated by this method with the + * parameter name and value filled in. Any other exceptions thrown by converter + * will be wrapped in ConversionFailedException and rethrown.

+ * + * IllegalStateException is thrown if the request has multiple values for the parameter. + */ + public T require (String name, Function converter) + { + return convert(converter, name, require(name)); + } + + /** + * Returns all the values for the given parameter on the request, or defaultValues + * if it has no such parameter. + */ + public String[] getAll (String name, String... defaultValues) + { + String[] vals = _req.getParameterValues(name); + if (vals == null) { + return defaultValues; + } + return vals; + } + + /** + * Returns all the values for the given parameter on the request as converted by + * converter, or defaultValues if it has no such parameter.

+ * + * If converter can't handle the value and throws + * ConversionFailedException, that will be propagated by this method with the + * parameter name and value filled in. Any other exceptions thrown by converter + * will be wrapped in ConversionFailedException and rethrown. + */ + public Collection getAll (final String name, final Function converter, + T... defaultValues) + { + String[] vals = _req.getParameterValues(name); + if (vals == null) { + return Arrays.asList(defaultValues); + } + return Lists.transform(Arrays.asList(vals), new Function(){ + public T apply (String from) { + return convert(converter, name, from); + }}); + } + + /** + * Returns the result of passing the given value into converter. If an exception is thrown by + * it, a ConversionFailedException is thrown with the name and value filled in as part of its + * message. + */ + protected static T convert (Function converter, String name, String val) + { + try { + return converter.apply(val); + } catch (RuntimeException re) { + ConversionFailedException fail; + if (re instanceof ConversionFailedException) { + fail = (ConversionFailedException)re; + } else { + fail = new ConversionFailedException(re, "Failed converting parameter"); + } + fail.append("name", name, "value", val); + throw fail; + } + } + + protected final ServletRequest _req; +} diff --git a/src/main/java/com/threerings/servlet/util/QueryBuilder.java b/src/main/java/com/threerings/servlet/util/QueryBuilder.java new file mode 100644 index 0000000..b3a65bf --- /dev/null +++ b/src/main/java/com/threerings/servlet/util/QueryBuilder.java @@ -0,0 +1,117 @@ +// +// $Id$ + +package com.threerings.servlet.util; + +import java.util.Collection; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + +import com.samskivert.util.StringUtil; +import com.samskivert.util.Tuple; + +public class QueryBuilder +{ + /** + * Retrieve the only value for the given key, or null if it's not defined. If the key has more + * than one value defined, an IllegalArgumentException will be thrown. + */ + public String getOnly (String key) + { + return getOnly(key, null); + } + + /** + * Retrieve the only value for the given key, or the default value if it's not defined. If the + * key has more than one value defined, an IllegalArgumentException will be thrown. + */ + public String getOnly (String key, String defaultValue) + { + return Iterables.getOnlyElement(get(key), defaultValue); + } + + /** + * Retrieve the first value for the given key, or null if the key is not defined. + */ + public String getFirst (String key) + { + Collection values = get(key); + return values.isEmpty() ? null : values.iterator().next(); + } + + /** + * Returns the current values for key or an empty collection if there isn't a value. + */ + public Collection get (String key) + { + return _params.get(key); + } + + /** + * Adds the given value to the parameter mapping under key adding to any current value. + * {@link String#valueOf} is called on val to turn it into a string. + */ + public QueryBuilder add (String key, Object val) + { + _params.put(key, String.valueOf(val)); + return this; + } + + /** + * Adds all the parameters in req to the parameters being constructed. + */ + public QueryBuilder addAll (HttpServletRequest req) + { + for (Tuple entry : new Parameters(req).entries()) { + add(entry.left, entry.right); + } + return this; + } + + /** + * Appends the query in this builder to the given StringBuilder with a question mark. + */ + public String toUrl (StringBuilder base) + { + return _params.isEmpty() ? base.toString() : encode(base.append('?'), _params.entries()); + } + + /** + * Appends the query in this builder to the given String with a question mark. + */ + public String toUrl (String base) + { + return toUrl(new StringBuilder(base)); + } + + /** + * Returns the query in this builder. + */ + public String toString () + { + return encode(new StringBuilder(), _params.entries()); + } + + protected static String encode (StringBuilder out, Iterable> entries) + { + boolean appended = false; + for (Map.Entry entry : entries) { + out.append(StringUtil.encode(entry.getKey())). + append('='). + append(StringUtil.encode(entry.getValue())). + append('&'); + appended = true; + } + if (appended) { // Drop the extra ampersand + out.setLength(out.length() - 1); + } + return out.toString(); + } + + protected final Multimap _params = ArrayListMultimap.create(); +}