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.
This commit is contained in:
Michael Bayne
2011-10-05 09:55:15 -07:00
commit 6ab4205054
9 changed files with 602 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" standalone="yes"?>
<project name="ooo-user" default="compile" basedir=".">
<property name="src.dir" location="src/main/java"/>
<property name="deploy.dir" location="target"/>
<property name="classes.dir" location="${deploy.dir}/classes"/>
<property name="jar.file" location="${deploy.dir}/${ant.project.name}.jar"/>
<!-- bring in our standard build support -->
<property name="ooo-build.vers" value="2.7"/>
<ant antfile="etc/bootstrap.xml"/>
<import file="${user.home}/.m2/ooo-build/${ooo-build.vers}/ooo-build.xml"/>
<target name="prepare" depends="-init-ooo">
<mkdir dir="${classes.dir}"/>
<mavendep pom="pom.xml"/>
</target>
<target name="clean" description="Cleans out compilation results.">
<delete dir="${deploy.dir}"/>
</target>
<target name="compile" depends="prepare" description="Builds the Java code.">
<ooojavac srcdir="${src.dir}" destdir="${classes.dir}" version="1.5"
classpathref="pom.xml.path"/>
</target>
<target name="package" depends="compile" description="Builds and jars the code.">
<jar destfile="${jar.file}" basedir="${classes.dir}"/>
</target>
<target name="maven-deploy" depends="package"
description="Deploys our build artifacts to a Maven repository.">
<mavendeploy file="${jar.file}" pom="pom.xml" srcdir="${src.dir}"/>
</target>
</project>
+36
View File
@@ -0,0 +1,36 @@
<project name="bootstrap" default="-extract-ooo-build">
<!--
Pulls in ooo-build from our maven repository to make our base build system available.
From http://ooo-build.googlecode.com/hg/etc/bootstrap.xml
To use, copy this file into your project's directory and add
<property name="ooo-build.vers" value="2.1"/>
<ant antfile="bootstrap.xml"/>
<import file="${user.home}/.m2/ooo-build/${ooo-build.vers}/ooo-build.xml"/>
to the top level of your build.xml. Then you can depend on -init-ooo in your lowest level
target to expose ooo-build's maven macros, javac macros, and antcontrib.
-->
<property name="maven.dir" value="${user.home}/.m2"/>
<property name="ooo-build.path"
value="com/threerings/ooo-build/${ooo-build.vers}"/>
<property name="ooo-build.jar"
value="ooo-build-${ooo-build.vers}.jar"/>
<property name="ooo-build.local.dir" value="${maven.dir}/repository/${ooo-build.path}"/>
<property name="ooo-build.local.file" value="${ooo-build.local.dir}/${ooo-build.jar}"/>
<condition property="ooo-build.exists"><available file="${ooo-build.local.file}"/></condition>
<target name="-download-ooo-build" unless="ooo-build.exists">
<mkdir dir="${ooo-build.local.dir}"/>
<get src="http://ooo-maven.googlecode.com/hg/repository/${ooo-build.path}/${ooo-build.jar}"
dest="${ooo-build.local.file}" usetimestamp="true"/>
</target>
<property name="ooo-build.dir" value="${maven.dir}/ooo-build/${ooo-build.vers}"/>
<condition property="extracted.exists"><available file="${ooo-build.dir}/ooo-build.xml"/></condition>
<target name="-extract-ooo-build" depends="-download-ooo-build" unless="extracted.exists">
<mkdir dir="${ooo-build.dir}"/>
<unjar src="${ooo-build.local.file}" dest="${ooo-build.dir}"/>
</target>
</project>
+50
View File
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.threerings</groupId>
<artifactId>ooo-parent</artifactId>
<version>1.0</version>
</parent>
<artifactId>ooo-util</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>ooo-util</name>
<dependencies>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>samskivert</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>10.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<links>
<link>http://samskivert.googlecode.com/svn/apidocs/</link>
<link>http://docs.guava-libraries.googlecode.com/git/javadoc/</link>
</links>
</configuration>
</plugin>
</plugins>
</build>
</project>
+15
View File
@@ -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");
}
@@ -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;
}
@@ -0,0 +1,39 @@
//
// $Id$
package com.threerings.servlet.util;
import com.google.common.base.Function;
public class Converters
{
public static final Function<String, Long> TO_LONG = new Function<String, Long>() {
public Long apply (String from) {
return Long.parseLong(from);
}
};
public static final Function<String, Boolean> TO_BOOLEAN = new Function<String, Boolean>() {
public Boolean apply (String from) {
return Boolean.parseBoolean(from);
}
};
public static final Function<String, Integer> TO_INT = new Function<String, Integer>() {
public Integer apply (String from) {
return Integer.parseInt(from);
}
};
public static final Function<String, Byte> TO_BYTE = new Function<String, Byte>() {
public Byte apply (String from) {
return Byte.parseByte(from);
}
};
public static final Function<String, Float> TO_FLOAT = new Function<String, Float>() {
public Float apply (String from) {
return Float.parseFloat(from);
}
};
}
@@ -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 <code>val</code> 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 <code>req</code> 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<String, String> 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<String, String> _params = Maps.newHashMap();
}
@@ -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<String> names ()
{
@SuppressWarnings("unchecked")
Set<String> names = _req.getParameterMap().keySet();
return names;
}
/**
* Returns a Collection of all the parameter values for this request.
*/
public Collection<String[]> values ()
{
@SuppressWarnings("unchecked")
Collection<String[]> 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<Tuple<String, String>> entries ()
{
Set<String> names = names();
List<Tuple<String, String>> 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 <code>def</code> 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 <code>converter</code>
* or <code>def</code>if it has no such parameter.<p>
*
* If <code>converter</code> can't handle the value and throws
* <code>ConversionFailedException</code>, that will be propagated by this method with the
* parameter name and value filled in. Any other exceptions thrown by <code>converter</code>
* will be wrapped in <code>ConversionFailedException</code> and rethrown.<p>
*
* IllegalStateException is thrown if the request has multiple values for the parameter.
*/
public <T> T get (String name, Function<String, T> 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
* <code>NullPointerException</code> 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 <code>converter</code>
* or throws <code>NullPointerException</code> if it has no such parameter.<p>
*
* If <code>converter</code> can't handle the value and throws
* <code>ConversionFailedException</code>, that will be propagated by this method with the
* parameter name and value filled in. Any other exceptions thrown by <code>converter</code>
* will be wrapped in <code>ConversionFailedException</code> and rethrown.<p>
*
* IllegalStateException is thrown if the request has multiple values for the parameter.
*/
public <T> T require (String name, Function<String, T> converter)
{
return convert(converter, name, require(name));
}
/**
* Returns all the values for the given parameter on the request, or <code>defaultValues</code>
* 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
* <code>converter</code>, or <code>defaultValues</code> if it has no such parameter.<p>
*
* If <code>converter</code> can't handle the value and throws
* <code>ConversionFailedException</code>, that will be propagated by this method with the
* parameter name and value filled in. Any other exceptions thrown by <code>converter</code>
* will be wrapped in <code>ConversionFailedException</code> and rethrown.
*/
public <T> Collection<T> getAll (final String name, final Function<String, T> converter,
T... defaultValues)
{
String[] vals = _req.getParameterValues(name);
if (vals == null) {
return Arrays.asList(defaultValues);
}
return Lists.transform(Arrays.asList(vals), new Function<String, T>(){
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> T convert (Function<String, T> 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;
}
@@ -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<String> 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<String> 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 <code>val</code> 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 <code>req</code> to the parameters being constructed.
*/
public QueryBuilder addAll (HttpServletRequest req)
{
for (Tuple<String, String> 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<Map.Entry<String, String>> entries)
{
boolean appended = false;
for (Map.Entry<String, String> 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<String, String> _params = ArrayListMultimap.create();
}