You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by nb...@apache.org on 2008/10/10 06:47:35 UTC
svn commit: r703317 - in /velocity/tools/trunk/src:
main/java/org/apache/velocity/tools/generic/LinkTool.java
test/java/org/apache/velocity/tools/LinkToolTests.java
Author: nbubna
Date: Thu Oct 9 21:47:32 2008
New Revision: 703317
URL: http://svn.apache.org/viewvc?rev=703317&view=rev
Log:
greatly improve parameter handling, make absolute/relative stuff work sanely, and generally improve the consistency and usefulness of this generic LinkTool version
Modified:
velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LinkTool.java
velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LinkToolTests.java
Modified: velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LinkTool.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LinkTool.java?rev=703317&r1=703316&r2=703317&view=diff
==============================================================================
--- velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LinkTool.java (original)
+++ velocity/tools/trunk/src/main/java/org/apache/velocity/tools/generic/LinkTool.java Thu Oct 9 21:47:32 2008
@@ -87,6 +87,8 @@
/** XHTML delimiter for query data ('&') */
public static final String XHTML_QUERY_DELIMITER = "&";
+ public static final String APPEND_PARAMS_KEY = "appendParameters";
+ public static final String FORCE_RELATIVE_KEY = "forceRelative";
public static final String DEFAULT_CHARSET = "UTF-8";
public static final String DEFAULT_SCHEME = "http";
public static final String SECURE_SCHEME = "https";
@@ -108,10 +110,12 @@
protected String host;
protected int port;
protected String path;
- protected String query;
+ protected Map query;
protected String fragment;
protected String charset;
protected String queryDelim;
+ protected boolean appendParams;
+ protected boolean forceRelative;
private boolean opaque;
private final LinkTool self;
@@ -132,6 +136,8 @@
charset = DEFAULT_CHARSET;
queryDelim = XHTML_QUERY_DELIMITER;
opaque = false;
+ appendParams = true;
+ forceRelative = false;
self = this;
}
@@ -203,6 +209,16 @@
{
setXHTML(xhtml);
}
+ Boolean addParams = props.getBoolean(APPEND_PARAMS_KEY);
+ if (addParams != null)
+ {
+ setAppendParams(addParams);
+ }
+ Boolean forceRelative = props.getBoolean(FORCE_RELATIVE_KEY);
+ if (forceRelative != null)
+ {
+ setForceRelative(forceRelative);
+ }
}
/**
@@ -212,9 +228,25 @@
*/
protected LinkTool duplicate()
{
+ return duplicate(false);
+ }
+
+ /**
+ * Equivalent to clone, but with no checked exceptions.
+ * If for some unfathomable reason clone() doesn't work,
+ * this will throw a RuntimeException. If doing a deep
+ * clone, then the parameter Map will also be cloned.
+ */
+ protected LinkTool duplicate(boolean deep)
+ {
try
{
- return (LinkTool)this.clone();
+ LinkTool that = (LinkTool)this.clone();
+ if (deep && query != null)
+ {
+ that.query = new LinkedHashMap(query);
+ }
+ return that;
}
catch (CloneNotSupportedException e)
{
@@ -250,6 +282,29 @@
}
/**
+ * Sets whether or not the {@link #setParam(Object,Object)} method
+ * will override existing query values for the same key or simply append
+ * the new value to a list of existing values.
+ */
+ public void setAppendParams(boolean addParams)
+ {
+ this.appendParams = addParams;
+ }
+
+ /**
+ * Sets whether or not the {@link #createURI} method should ignore the
+ * scheme, user, port and host values for non-opaque URIs, thus making
+ * {@link #toString} print the link as a relative one, not an absolute
+ * one. NOTE: using {@link #absolute()}, {@link #absolute(Object)},
+ * {@link #relative()}, or {@link #relative(Object)} will alter this
+ * setting accordingly on the new instances they return.
+ */
+ public void setForceRelative(boolean forceRelative)
+ {
+ this.forceRelative = forceRelative;
+ }
+
+ /**
* This will treat empty strings like null values
* and will trim any trailing ':' character.
*/
@@ -382,12 +437,11 @@
}
/**
- * This will trim any '?' character at the start of the
- * specified value. It will also check the value to see if
- * it already includes multiple query pairs by checking for
- * any '&' characters in the string. If there are some, then
- * the delimiters between the pairs will all be replaced
- * with this instance's configured query delimiter.
+ * If the specified value is null, it will set the query to null.
+ * If a Map, it will copy all those values into a new LinkedHashMap and
+ * replace any current query value with that. If it is a String,
+ * it will use {@link #parseQuery(String)} to parse it into a map
+ * of keys to values.
*/
public void setQuery(Object obj)
{
@@ -395,30 +449,38 @@
{
this.query = null;
}
+ else if (obj instanceof Map)
+ {
+ this.query = new LinkedHashMap((Map)obj);
+ }
else
{
- this.query = String.valueOf(obj);
- if (query.startsWith("?"))
- {
- this.query = query.substring(1, query.length());
- }
- // if we have multiple pairs...
- if (query.contains("&"))
- {
- // ensure the delimeters match the xhtml setting
- // this impl is not at all efficient, but it's easy
- this.query = query.replaceAll("&(amp;)?", queryDelim);
- }
+ String qs = normalizeQuery(String.valueOf(obj));
+ this.query = parseQuery(qs);
+ }
+ }
+
+ protected String normalizeQuery(String qs)
+ {
+ // if we have multiple pairs...
+ if (qs.indexOf('&') >= 0)
+ {
+ // ensure the delimeters match the xhtml setting
+ // this impl is not at all efficient, but it's easy
+ qs = qs.replaceAll("&(amp;)?", queryDelim);
}
+ return qs;
}
/**
- * Converts the map of keys to values into a query string
- * and uses {@link #appendQuery(Object)} to add it to the
- * current {@link #getQuery} value.
+ * Converts the map of keys to values into a query string.
*/
- public void appendQuery(Map parameters)
+ public String toQuery(Map parameters)
{
+ if (parameters == null)
+ {
+ return null;
+ }
StringBuilder query = new StringBuilder();
for (Object e : parameters.entrySet())
{
@@ -430,7 +492,7 @@
}
query.append(toQuery(entry.getKey(), entry.getValue()));
}
- appendQuery(query);
+ return query.toString();
}
/**
@@ -446,6 +508,140 @@
}
/**
+ * If there is no existing value for this key in the query, it
+ * will simply add it and its value to the query. If the key
+ * already is present in the query and append
+ * is true, this will add the specified value to those
+ * already under that key. If {@link #appendParams} is
+ * false, this will override the existing values with the
+ * specified new value.
+ */
+ public void setParam(Object key, Object value, boolean append)
+ {
+ // use all keys as strings, even null -> "null"
+ key = String.valueOf(key);
+ if (this.query == null)
+ {
+ this.query = new LinkedHashMap();
+ query.put(key, value);
+ }
+ else if (append)
+ {
+ appendParam((String)key, value);
+ }
+ else
+ {
+ query.put(key, value);
+ }
+ }
+
+ private void appendParam(String key, Object value)
+ {
+ if (query.containsKey(key))
+ {
+ Object cur = query.get(key);
+ if (cur instanceof List)
+ {
+ addToList((List)cur, value);
+ }
+ else
+ {
+ List vals = new ArrayList();
+ vals.add(cur);
+ addToList(vals, value);
+ query.put(key, vals);
+ }
+ }
+ else
+ {
+ query.put(key, value);
+ }
+ }
+
+ private void addToList(List vals, Object value)
+ {
+ if (value instanceof List)
+ {
+ for (Object v : ((List)value))
+ {
+ vals.add(v);
+ }
+ }
+ else if (value instanceof Object[])
+ {
+ for (Object v : ((Object[])value))
+ {
+ vals.add(v);
+ }
+ }
+ else
+ {
+ vals.add(value);
+ }
+ }
+
+ /**
+ * If append is false, this simply delegates to {@link #setQuery}.
+ * Otherwise, if the specified object is null, it does nothing. If the object
+ * is not a Map, it will turn it into a String and use {@link #parseQuery} to
+ * parse it. Once it is a Map, it will iterate through the entries appending
+ * each key/value to the current query data.
+ */
+ public void setParams(Object obj, boolean append)
+ {
+ if (!append)
+ {
+ setQuery(obj);
+ }
+ else if (obj != null)
+ {
+ if (!(obj instanceof Map))
+ {
+ obj = parseQuery(String.valueOf(obj));
+ }
+ if (obj != null)
+ {
+ if (query == null)
+ {
+ this.query = new LinkedHashMap();
+ }
+ for (Object e : ((Map)obj).entrySet())
+ {
+ Map.Entry entry = (Map.Entry)e;
+ String key = String.valueOf(entry.getKey());
+ appendParam(key, entry.getValue());
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes the query pair(s) with the specified key from the
+ * query data and returns the remove value(s), if any.
+ */
+ public Object removeParam(Object key)
+ {
+ if (query != null)
+ {
+ key = String.valueOf(key);
+ return query.remove(key);
+ }
+ return null;
+ }
+
+ /**
+ * In this class, this method ignores true values. If passed a false value,
+ * it will call {@link #setQuery} with a null value to clear all query data.
+ */
+ protected void handleParamsBoolean(boolean keep)
+ {
+ if (!keep)
+ {
+ setQuery(null);
+ }
+ }
+
+ /**
* If the second param is null or empty, this will simply return the first
* and vice versa. Otherwise, it will trim any '?'
* at the start of the second param and any '&' or '&' at the
@@ -538,12 +734,14 @@
}
/**
- * Uses {@link #parseQuery(String,String)} to parse the specified
- * query string using this instance's current query delimiter.
+ * Uses {@link #normalizeQuery} to make all delimiters in the
+ * specified query string match the current query delimiter
+ * and then uses {@link #parseQuery(String,String)} to parse it
+ * according to that same delimiter.
*/
protected Map<String,Object> parseQuery(String query)
{
- return parseQuery(query, this.queryDelim);
+ return parseQuery(normalizeQuery(query), this.queryDelim);
}
/**
@@ -632,22 +830,10 @@
return true;
}
- URI uri;
- if (obj instanceof URI)
- {
- uri = (URI)obj;
- }
- else
+ URI uri = toURI(obj);
+ if (uri == null)
{
- try
- {
- uri = new URI(String.valueOf(obj));
- }
- catch (Exception e)
- {
- debug("Could convert '%s' to URI", e, obj);
- return false;
- }
+ return false;
}
setScheme(uri.getScheme());
if (uri.isOpaque())
@@ -674,6 +860,29 @@
}
/**
+ * Turns the specified object into a string and thereby a URI.
+ */
+ protected URI toURI(Object obj)
+ {
+ if (obj instanceof URI)
+ {
+ return (URI)obj;
+ }
+ else
+ {
+ try
+ {
+ return new URI(String.valueOf(obj));
+ }
+ catch (Exception e)
+ {
+ debug("Could convert '%s' to URI", e, obj);
+ return null;
+ }
+ }
+ }
+
+ /**
* Tries to create a URI from the current port, opacity, scheme,
* userInfo, host, path, query and fragment set for this instance,
* using the {@link URI} constructor that is appropriate to the opacity.
@@ -695,6 +904,14 @@
// path is used as scheme-specific part
return new URI(scheme, path, anchor);
}
+ else if (forceRelative)
+ {
+ if (path == null && query == null && fragment == null)
+ {
+ return null;
+ }
+ return new URI(null, null, null, -1, path, toQuery(query), anchor);
+ }
else
{
// only create the URI if we have some values besides a port
@@ -703,7 +920,7 @@
{
return null;
}
- return new URI(scheme, user, host, port, path, query, fragment);
+ return new URI(scheme, user, host, port, path, toQuery(query), anchor);
}
}
}
@@ -735,6 +952,15 @@
return queryDelim.equals(XHTML_QUERY_DELIMITER);
}
+ /**
+ * Returns true if {@link #param(Object,Object)} appends values;
+ * false if the method overwrites existing value(s) for the specified key.
+ */
+ public boolean getAppendParams()
+ {
+ return this.appendParams;
+ }
+
/**
* Returns a new instance with the specified value set as its scheme.
@@ -779,18 +1005,6 @@
}
/**
- * Returns true if this instance has a scheme value.
- */
- public boolean isAbsolute()
- {
- if (this.scheme == null)
- {
- return false;
- }
- return true;
- }
-
- /**
* Returns true if this instance represents an opaque URI.
* @see URI
*/
@@ -902,40 +1116,40 @@
}
/**
- * At this level, this method returns all "directories"
+ * Returns the directory stack
* in the set {@link #getPath()} value, by just trimming
- * of the last "/" and all that follows.
+ * off all that follows the last "/".
*/
- public String getContextPath()
+ public String getDirectory()
{
if (this.path == null || this.opaque)
{
return null;
}
int lastSlash = this.path.lastIndexOf('/');
- if (lastSlash <= 0)
+ if (lastSlash < 0)
{
return "";
}
- return this.path.substring(0, lastSlash);
+ return this.path.substring(0, lastSlash + 1);
}
/**
- * At this level, this method returns the last section
- * of the path, from the final "/" onward.
+ * Returns the last section of the path,
+ * which is all that follows the final "/".
*/
- public String getRequestPath()
+ public String getFile()
{
if (this.path == null || this.opaque)
{
return null;
}
int lastSlash = this.path.lastIndexOf('/');
- if (lastSlash <= 0)
+ if (lastSlash < 0)
{
return this.path;
}
- return this.path.substring(lastSlash, this.path.length());
+ return this.path.substring(lastSlash + 1, this.path.length());
}
/**
@@ -949,117 +1163,198 @@
*/
public String getRoot()
{
- if (host == null || opaque || port == -2)
+ LinkTool root = root();
+ if (root == null)
{
return null;
}
- if (scheme == null)
- {
- scheme = DEFAULT_SCHEME;
- }
- StringBuilder out = new StringBuilder();
- out.append(scheme);
- out.append("://");
- out.append(host);
- // if we have a port that's not a default for the scheme
- if (port >= 0 &&
- ((scheme.equals(DEFAULT_SCHEME) && port != 80) ||
- (isSecure() && port != 443)))
+ return root.toString();
+ }
+
+ /**
+ * Returns a new LinkTool instance that represents
+ * the "root" of the current one, if it has one.
+ * This essentially calls {@link #absolute()} and
+ * sets the path, query, and fragment to null on
+ * the returned instance.
+ * @see #getRoot()
+ */
+ public LinkTool root()
+ {
+ if (host == null || opaque || port == -2)
{
- out.append(':');
- out.append(port);
+ return null;
}
- return out.toString();
+ LinkTool copy = absolute();
+ copy.setPath(null);
+ copy.setQuery(null);
+ copy.setFragment(null);
+ return copy;
}
-
/**
- * <p>Returns the URI that addresses the current "directory"
- * of this instance. This string does not end with a "/" and
- * is essentially a concatenation of {@link #getRoot} and
- * {@link #getContextPath}</p>
+ * Returns a new LinkTool instance with
+ * the path set to the result of {@link #getDirectory()}
+ * and the query and fragment set to null.
*/
- public String getContextURL()
+ public LinkTool directory()
{
- String root = getRoot();
- if (root == null)
+ LinkTool copy = root();
+ if (copy == null)
{
- return null;
+ copy = duplicate();
+ // clear query and fragment, since root() didn't
+ copy.setQuery(null);
+ copy.setFragment(null);
}
- return combinePath(root, getContextPath());
+ copy.setPath(getDirectory());
+ return copy;
}
/**
- * <p>Returns a copy of the link with the specified context-relative
- * URI reference converted to a server-relative URI reference. This
- * method will overwrite any previous URI reference settings but will
- * copy the query string.</p>
+ * Returns true if this instance is being forced to
+ * return relative URIs or has a null scheme value.
+ */
+ public boolean isRelative()
+ {
+ return (this.forceRelative || this.scheme == null);
+ }
+
+ /**
+ * Returns a copy of this LinkTool instance that has
+ * {@link #setForceRelative} set to true.
+ */
+ public LinkTool relative()
+ {
+ LinkTool copy = duplicate();
+ copy.setForceRelative(true);
+ return copy;
+ }
+
+ /**
+ * <p>Returns a copy of the link with the specified directory-relative
+ * URI reference set as the end of the path and {@link #setForceRelative}
+ * set to true.</p>
*
* Example:<br>
- * <code><a href='$link.relative("/templates/login/index.vm")'>Login Page</a></code><br>
+ * <code><a href='$link.relative("/login/index.vm")'>Login Page</a></code><br>
* produces something like</br>
- * <code><a href="/myapp/templates/login/index.vm">Login Page</a></code><br>
+ * <code><a href="/myapp/login/index.vm">Login Page</a></code><br>
*
- * @param obj A context-relative URI reference. A context-relative URI
- * is a URI that is relative to the root of this web application.
- * @return a new instance of LinkTool with the specified URI
+ * @param obj A directory-relative URI reference (e.g. file path in current directory)
+ * @return a new instance of LinkTool with the specified changes
+ * @see #relative()
*/
public LinkTool relative(Object obj)
{
if (obj == null)
{
- return path(getContextPath());
+ return path(getDirectory());
}
String pth = String.valueOf(obj);
+ LinkTool copy = relative();
+ // prepend relative paths with the current directory
+ copy.setPath(combinePath(getDirectory(), pth));
+ return copy;
+ }
+
+ /**
+ * Returns true if this instance has a scheme value
+ * and is not being forced to create relative URIs.
+ */
+ public boolean isAbsolute()
+ {
+ return (this.scheme != null && !this.forceRelative);
+ }
+
+ /**
+ * Returns a copy of this LinkTool instance that has
+ * {@link #setForceRelative} set to false and sets the
+ * scheme to the "http" if no scheme has been set yet.
+ */
+ public LinkTool absolute()
+ {
LinkTool copy = duplicate();
- // prepend relative paths with context path
- copy.setPath(combinePath(getContextPath(), pth));
+ copy.setForceRelative(false);
+ if (copy.getScheme() == null)
+ {
+ copy.setScheme(DEFAULT_SCHEME);
+ }
return copy;
}
/**
* <p>Returns a copy of the link with the specified URI reference
* either used as or converted to an absolute (non-relative)
- * URI reference. This method will overwrite any previous URI
- * reference settings but will copy the query string.</p>
+ * URI reference. Unless the specified URI contains a query
+ * or anchor, those values will not be overwritten when using
+ * this method.</p>
*
* Example:<br>
- * <code><a href='$link.absolute("/templates/login/index.vm")'>Login Page</a></code><br>
+ * <code><a href='$link.absolute("login/index.vm")'>Login Page</a></code><br>
* produces something like<br/>
- * <code><a href="http://myserver.net/myapp/templates/login/index.vm">Login Page</a></code><br>
+ * <code><a href="http://myserver.net/myapp/login/index.vm">Login Page</a></code>;<br>
+ * <code><a href='$link.absolute("/login/index.vm")'>Login Page</a></code><br>
+ * produces something like<br/>
+ * <code><a href="http://myserver.net/login/index.vm">Login Page</a></code>;<br>
* and<br>
* <code><a href='$link.absolute("http://theirserver.com/index.jsp")'>Their, Inc.</a></code><br>
* produces something like<br/>
* <code><a href="http://theirserver.net/index.jsp">Their, Inc.</a></code><br>
*
- * @param obj A context-relative URI reference or absolute URL.
- * @return a new instance of LinkTool with the specified URI
- * @see #uri(Object uri)
- * @since VelocityTools 1.3
+ * @param obj A root-relative or context-relative path or an absolute URI.
+ * @return a new instance of LinkTool with the specified path or URI
+ * @see #absolute()
*/
public LinkTool absolute(Object obj)
{
+ // assume it's just a path value to go with current scheme/host/port
+ LinkTool copy = absolute();
+ String pth;
if (obj == null)
{
- // use uri's null handling
- return uri(obj);
+ // just use the current directory path, if any
+ pth = getDirectory();
}
- String pth = String.valueOf(obj);
- if (pth.startsWith(DEFAULT_SCHEME))
- {
- // looks absolute already
- return uri(pth);
- }
-
- // assume it's relative and try to make it absolute
- String root = getRoot();
- if (root != null)
+ else
{
- return uri(combinePath(root, pth));
+ pth = String.valueOf(obj);
+ if (pth.startsWith(DEFAULT_SCHEME))
+ {
+ // looks absolute already
+ URI uri = toURI(pth);
+ if (uri == null)
+ {
+ return null;
+ }
+ copy.setScheme(uri.getScheme());
+ copy.setUserInfo(uri.getUserInfo());
+ copy.setHost(uri.getHost());
+ copy.setPort(uri.getPort());
+ // handle path, query and fragment with care
+ pth = uri.getPath();
+ if (pth.equals("/") || pth.length() == 0)
+ {
+ pth = null;
+ }
+ copy.setPath(pth);
+ if (uri.getQuery() != null)
+ {
+ copy.setQuery(uri.getQuery());
+ }
+ if (uri.getFragment() != null)
+ {
+ copy.setFragment(uri.getFragment());
+ }
+ return copy;
+ }
+ else if (!pth.startsWith("/"))
+ {
+ // paths that don't start with '/'
+ // are considered relative to the current directory
+ pth = combinePath(getDirectory(), pth);
+ }
}
- // ugh. this is about all we can do here w/o a host
- LinkTool copy = duplicate();
- copy.setScheme(DEFAULT_SCHEME);
copy.setPath(pth);
return copy;
}
@@ -1073,7 +1368,6 @@
*
* @param uri URI reference to set
* @return a new instance of LinkTool
- * @since VelocityTools 1.3
*/
public LinkTool uri(Object uri)
{
@@ -1117,8 +1411,9 @@
}
/**
- * Sets the specified value as the current query string,
- * after normalizing the pair delimiters.
+ * Sets the specified value as the current query data,
+ * after normalizing the pair delimiters. This overrides
+ * any existing query.
*/
public LinkTool query(Object query)
{
@@ -1128,60 +1423,121 @@
}
/**
- * Returns the current query string, if any.
+ * Returns the current query as a string, if any.
*/
public String getQuery()
{
- return this.query;
+ return toQuery(this.query);
}
/**
- * <p>Adds a key=value pair to the query data. This returns a new LinkTool
- * containing both a copy of this LinkTool's query data and the new data.
- * Query data is URL encoded before it is appended.</p>
+ * <p>Adds a key=value pair to the query data. Whether
+ * this new query pair is appended to the current query
+ * or overwrites any previous pair(s) with the same key
+ * is controlled by the {@link #getAppendsParam} value.
+ * The default behavior is to append.</p>
*
* @param key key of new query parameter
* @param value value of new query parameter
- *
* @return a new instance of LinkTool
- * @since VelocityTools 1.3
*/
public LinkTool param(Object key, Object value)
{
- LinkTool copy = duplicate();
- copy.appendQuery(toQuery(key, value));
+ LinkTool copy = duplicate(true);
+ copy.setParam(key, value, this.appendParams);
return copy;
}
/**
- * <p>Adds multiple key=value pairs to the query data.
- * This returns a new LinkTool containing both a copy of
- * this LinkTool's query data and the new data.
- * Query data is URL encoded before it is appended.</p>
+ * Appends a new key=value pair to the existing query
+ * data.
*
- * @param parameters map of new query data keys to values
+ * @param key key of new query parameter
+ * @param value value of new query parameter
* @return a new instance of LinkTool
- * @since VelocityTools 1.3
*/
- public LinkTool params(Map parameters)
+ public LinkTool append(Object key, Object value)
+ {
+ LinkTool copy = duplicate(true);
+ copy.setParam(key, value, true);
+ return copy;
+ }
+
+ /**
+ * Sets a new key=value pair to the existing query
+ * data, overwriting any previous pair(s) that have
+ * the same key.
+ *
+ * @param key key of new query parameter
+ * @param value value of new query parameter
+ * @return a new instance of LinkTool
+ */
+ public LinkTool set(Object key, Object value)
+ {
+ LinkTool copy = duplicate(true);
+ copy.setParam(key, value, false);
+ return copy;
+ }
+
+ /**
+ * Returns a new LinkTool instance that has any
+ * value(s) under the specified key removed from the query data.
+ *
+ * @param key key of the query pair(s) to be removed
+ * @return a new instance of LinkTool
+ */
+ public LinkTool remove(Object key)
+ {
+ LinkTool copy = duplicate(true);
+ copy.removeParam(key);
+ return copy;
+ }
+
+ /**
+ * This method can do two different things. If you pass in a
+ * boolean, it will create a new LinkTool duplicate and call
+ * {@link #handleParamsBoolean(boolean)} on it. In this class, true
+ * values do nothing (subclasses may have use for them), but false
+ * values will clear out all params in the query for that instance.
+ * If you pass in a query string or a Map of parameters, those
+ * values will be added to the new LinkTool, either overwriting
+ * previous value(s) with those keys or appending to them,
+ * depending on the {@link #getAppendParams} value.
+ *
+ * @param parameters a boolean or new query data (either Map or query string)
+ * @return a new instance of LinkTool
+ */
+ public LinkTool params(Object parameters)
{
// don't waste time with null/empty data
- if (parameters == null || parameters.isEmpty())
+ if (parameters == null)
{
return this;
}
- LinkTool copy = duplicate();
- copy.appendQuery(parameters);
+ if (parameters instanceof Boolean)
+ {
+ Boolean action = ((Boolean)parameters).booleanValue();
+ LinkTool copy = duplicate(true);
+ copy.handleParamsBoolean(action);
+ return copy;
+ }
+ if (parameters instanceof Map && ((Map)parameters).isEmpty())
+ {
+ return duplicate(false);
+ }
+
+ LinkTool copy = duplicate(this.appendParams);
+ copy.setParams(parameters, this.appendParams);
return copy;
}
public Map getParams()
{
- if (this.query == null || this.query.length() == 0)
+ if (this.query == null || this.query.isEmpty())
{
return null;
}
- return parseQuery(this.query);
+ return this.query;
}
/**
@@ -1195,7 +1551,6 @@
*
* @param anchor an internal document reference
* @return a new instance of LinkTool with the set anchor
- * @since VelocityTools 1.3
*/
public LinkTool anchor(Object anchor)
{
Modified: velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LinkToolTests.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LinkToolTests.java?rev=703317&r1=703316&r2=703317&view=diff
==============================================================================
--- velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LinkToolTests.java (original)
+++ velocity/tools/trunk/src/test/java/org/apache/velocity/tools/LinkToolTests.java Thu Oct 9 21:47:32 2008
@@ -105,11 +105,23 @@
assertSame(link.getUser(), result.getUser());
assertSame(link.getHost(), result.getHost());
assertSame(link.getPath(), result.getPath());
- assertSame(link.getQuery(), result.getQuery());
+ assertSame(link.query, result.query);
assertSame(link.getAnchor(), result.getAnchor());
assertSame(link.getSelf(), result.getSelf());
}
+ public @Test void methodDuplicate_boolean() throws Exception
+ {
+ LinkTool link = newInstance("http://apache.org/foo.html?foo=bar");
+ LinkTool result = link.duplicate(true);
+ assertFalse(link == result);
+ assertSame(link.getPath(), result.getPath());
+ assertFalse(link.query == result.query);
+ assertEquals(link.getQuery(), result.getQuery());
+ assertSame(link.getSelf(), result.getSelf());
+ assertSame(result.query, result.duplicate(false).query);
+ }
+
public @Test void methodEncode_Object() throws Exception
{
LinkTool link = newInstance();
@@ -337,20 +349,26 @@
assertEquals("/bar/foo", link.append("foo").getPath());
}
- public @Test void methodGetContextPath() throws Exception
+ public @Test void methodGetDirectory() throws Exception
{
LinkTool link = newInstance("http://foo.com/ctx/request.vm?this=that#anc");
- assertEquals("/ctx", link.getContextPath());
- link = newInstance("http://foo.com/foo/bar/request.vm?this=that#anc");
- assertEquals("/foo/bar", link.getContextPath());
+ assertEquals("/ctx/", link.getDirectory());
+ link = newInstance("http://foo.com");
+ assertNull(link.getDirectory());
+ link = newInstance("http://foo.com/bar");
+ assertEquals("/", link.getDirectory());
+ link = newInstance("http://foo.com/bar/foo/bar/foo");
+ assertEquals("/bar/foo/bar/", link.getDirectory());
}
- public @Test void methodGetRequestPath() throws Exception
+ public @Test void methodGetFile() throws Exception
{
LinkTool link = newInstance("http://foo.com/ctx/request.vm?this=that#anc");
- assertEquals("/request.vm", link.getRequestPath());
+ assertEquals("request.vm", link.getFile());
link = newInstance("http://foo.com/foo/bar/request.vm?this=that#anc");
- assertEquals("/request.vm", link.getRequestPath());
+ assertEquals("request.vm", link.getFile());
+ link = newInstance("http://foo.com/bar/");
+ assertEquals("", link.getFile());
}
public @Test void methodGetRoot() throws Exception
@@ -361,37 +379,64 @@
assertEquals("https://apache.org", link.secure().getRoot());
}
- public @Test void methodGetContextURL() throws Exception
+ public @Test void methodDirectory() throws Exception
{
LinkTool link = newInstance("http://foo.com/ctx/request.vm?this=that#anc");
- assertEquals("http://foo.com/ctx", link.getContextURL());
+ assertEquals("http://foo.com/ctx/", link.directory().toString());
link = newInstance("http://foo.com");
- assertEquals("http://foo.com", link.getContextURL());
+ assertEquals("http://foo.com", link.directory().toString());
+ }
+
+ public @Test void methodRoot() throws Exception
+ {
+ LinkTool link = newInstance("http://foo.com/ctx/request.vm?this=that#anc");
+ assertEquals("http://foo.com", link.root().toString());
+ link = newInstance("http://foo.com");
+ assertEquals("http://foo.com", link.root().toString());
+ link = newInstance("dev@velocity.apache.org");
+ assertNull(link.root());
+ }
+
+ public @Test void methodSetForceRelative_boolean() throws Exception
+ {
+ LinkTool link = newInstance("http://apache.org/bar");
+ assertEquals("http://apache.org/bar", link.toString());
+ link.setForceRelative(true);
+ assertEquals("/bar", link.toString());
}
public @Test void methodRelative_Object() throws Exception
{
LinkTool link = newInstance("/ctx/request.vm?this=that#anc");
assertEquals("/ctx/request.vm", link.getPath());
- assertEquals("/ctx", link.getContextPath());
+ assertEquals("/ctx/", link.getDirectory());
assertEquals("this=that", link.getQuery());
assertEquals("anc", link.getAnchor());
assertEquals("/ctx/request.vm?this=that#anc", link.toString());
- assertEquals("/ctx?this=that#anc", link.relative(null).toString());
+ assertEquals("/ctx/?this=that#anc", link.relative(null).toString());
assertEquals("/ctx/other.vm?this=that#anc", link.relative("other.vm").toString());
link = newInstance("http://foo.com/bar/");
- assertEquals("http://foo.com/bar/woogie.vm", link.relative("woogie.vm").toString());
+ assertEquals("/bar/woogie.vm", link.relative("woogie.vm").toString());
link = newInstance("/bar/");
assertEquals("/bar/foo/woogie.vm", link.relative("foo/woogie.vm").toString());
assertEquals("/bar/yo", link.relative("yo").toString());
}
+ public @Test void methodIsRelative() throws Exception
+ {
+ LinkTool link = newInstance("/ctx/request.vm?this=that#anc");
+ assertTrue(link.isRelative());
+ link = newInstance("http://foo.com/bar.vm?q=woogie");
+ assertFalse(link.isRelative());
+ assertTrue(link.relative().isRelative());
+ link = newInstance("http://apache.org").relative("foo.vm");
+ assertTrue(link.isRelative());
+ }
+
public @Test void methodAbsolute_Object() throws Exception
{
LinkTool link = newInstance();
LinkTool result = link.absolute(null);
- // nulls should be handled by uri()
- assertEquals(link.uri(null), result);
result = link.absolute("http://apache.org");
assertEquals(link.uri("http://apache.org"), result);
@@ -409,7 +454,9 @@
result = result.host("apache.org");
assertEquals("http://apache.org/test/foo.vm", result.toString());
result = result.absolute("bar.vm");
- assertEquals("http://apache.org/bar.vm", result.toString());
+ assertEquals("http://apache.org/test/bar.vm", result.toString());
+ result = result.absolute("/woogie.vm");
+ assertEquals("http://apache.org/woogie.vm", result.toString());
}
public @Test void methodGetBaseRef() throws Exception
@@ -442,6 +489,20 @@
assertEquals("velocity.apache.org", link.getHost());
}
+ public @Test void methodToURI_Object() throws Exception
+ {
+ LinkTool link = newInstance();
+ URI uri = new URI("http://apache.org");
+ assertSame(uri, link.toURI(uri));
+ assertNull(link.toURI("*%&$^%#$*&^!"));
+ assertNotNull(link.toURI("http://velocity.apache.org"));
+ assertNotNull(link.toURI(new Object() {
+ public String toString() {
+ return "http://google.com";
+ }
+ }));
+ }
+
public @Test void methodCreateURI() throws Exception
{
LinkTool link = newInstance();
@@ -471,6 +532,16 @@
assertEquals("mailto:nbubna@apache.org", link.uri(uri).toString());
}
+ public @Test void methodSetAppendParams_boolean() throws Exception
+ {
+ LinkTool link = newInstance("/bar?a=b");
+ LinkTool result = link.param("a","c");
+ assertEquals("a=b&a=c", result.getQuery());
+ link.setAppendParams(false);
+ result = link.param("a", "d");
+ assertEquals("a=d", result.getQuery());
+ }
+
public @Test void methodSetQuery_Object() throws Exception
{
LinkTool link = newInstance("/bar?a=b");
@@ -485,6 +556,15 @@
assertEquals(null, link.getQuery());
}
+ public @Test void methodNormalizeQuery_String() throws Exception
+ {
+ LinkTool link = new LinkTool();
+ assertEquals("a=b", link.normalizeQuery("a=b"));
+ assertEquals("a=b&b=c", link.normalizeQuery("a=b&b=c"));
+ assertEquals("a=b&b=c", link.normalizeQuery("a=b&b=c"));
+ assertEquals("a=b&b=c&t=f", link.normalizeQuery("a=b&b=c&t=f"));
+ }
+
public @Test void methodGetQuery() throws Exception
{
LinkTool link = newInstance();
@@ -532,6 +612,19 @@
assertEquals("z=3", link.getQuery());
}
+ public @Test void methodToQuery_Map() throws Exception
+ {
+ LinkTool link = new LinkTool();
+ Map test = new LinkedHashMap();
+ test.put("a", "b");
+ assertEquals("a=b", link.toQuery(test));
+ test.put("b", "c");
+ assertEquals("a=b&b=c", link.toQuery(test));
+ Boolean[] array = new Boolean[] { Boolean.TRUE, Boolean.FALSE };
+ test.put("b", array);
+ assertEquals("a=b&b=true&b=false", link.toQuery(test));
+ }
+
public @Test void methodToQuery_ObjectObject() throws Exception
{
LinkTool link = newInstance();
@@ -542,16 +635,103 @@
assertEquals("path=%2Ffoo+bar%2Fnew", link.toQuery("path", "/foo bar/new"));
}
+ public @Test void methodSetParam_ObjectObjectboolean() throws Exception
+ {
+ LinkTool link = newInstance();
+ assertNull(link.query);
+ link.setParam("a","b", true);
+ assertNotNull(link.query);
+ assertEquals("a=b", link.getQuery());
+ link.setParam("a", "c", true);
+ assertEquals("a=b&a=c", link.getQuery());
+ link.setParam("a", "foo", false);
+ assertEquals("a=foo", link.getQuery());
+ }
+
+ public @Test void methodSetParams_Objectboolean() throws Exception
+ {
+ LinkTool link = newInstance();
+ assertNull(link.query);
+ link.setParams("a=b", true);
+ assertNotNull(link.query);
+ assertEquals("a=b", link.getQuery());
+ link.setParams("a=c&a=d", true);
+ assertEquals("a=b&a=c&a=d", link.getQuery());
+ Map test = new LinkedHashMap();
+ test.put("a", "foo");
+ link.setParams(test, false);
+ assertEquals("a=foo", link.getQuery());
+ }
+
+ public @Test void methodParams_Object() throws Exception
+ {
+ LinkTool link = newInstance();
+ Map test = new LinkedHashMap();
+ test.put("a", "foo");
+ assertEquals("a=foo", link.params(test).getQuery());
+ assertEquals("a=b&b=c", link.params("a=b&b=c").getQuery());
+ link = newInstance("/foo?q=a&a=q");
+ assertEquals("/foo", link.params(false).toString());
+ assertEquals("/foo?q=a&a=q", link.params(true).toString());
+ }
+
public @Test void methodParam_ObjectObject() throws Exception
{
LinkTool link = newInstance();
- assertEquals("null=", link.param(null ,null).getQuery());
+ assertEquals("null=", link.param(null, null).getQuery());
assertEquals("x=1", link.param("x",1).getQuery());
assertEquals("x=1&y=2", link.param("x",1).param("y",2).getQuery());
link = newInstance("/hee/haa.vm?a=b");
assertEquals("/hee/haa.vm?a=b&b=true", link.param('b', true).toString());
}
+ public @Test void methodAppend_ObjectObject() throws Exception
+ {
+ LinkTool link = newInstance();
+ link.setAppendParams(false); //using append(key,val) should override
+ assertEquals("x=1", link.append("x",1).getQuery());
+ assertEquals("x=1&x=2", link.append("x",1).append("x",2).getQuery());
+ }
+
+ public @Test void methodSet_ObjectObject() throws Exception
+ {
+ LinkTool link = newInstance();
+ link.setAppendParams(true); //using set(key,val) should override
+ assertEquals("x=1", link.set("x",1).getQuery());
+ assertEquals("x=2", link.set("x",1).set("x",2).getQuery());
+ }
+
+ public @Test void methodRemove_Object() throws Exception
+ {
+ LinkTool link = newInstance("/foo?q=bar");
+ assertEquals("q=bar", link.getQuery());
+ assertEquals("", link.remove("q").getQuery());
+ assertEquals("q=bar", link.set("x",1).remove("x").getQuery());
+ }
+
+ public @Test void methodRemoveParam_Object() throws Exception
+ {
+ LinkTool link = newInstance();
+ assertNull(link.removeParam("a"));
+ link.setParam("a","b",true);
+ assertEquals("a=b", link.getQuery());
+ assertEquals("b", link.removeParam("a"));
+ assertNull(link.removeParam("a"));
+ }
+
+ public @Test void methodHandleParamsBoolean_boolean() throws Exception
+ {
+ LinkTool link = newInstance();
+ assertNull(link.query);
+ link.setParam("a","b",true);
+ Map q = link.query;
+ link.handleParamsBoolean(true);
+ assertSame(q, link.query);
+ assertEquals("a=b", link.getQuery());
+ link.handleParamsBoolean(false);
+ assertNull(link.query);
+ }
+
public @Test void methodParams_Map() throws Exception
{
LinkTool link = newInstance("http://go.com");
@@ -567,12 +747,14 @@
public @Test void methodParseQuery_String() throws Exception
{
LinkTool link = newInstance();
- Map result = link.parseQuery("a=b&x=1");
+ Map result = link.parseQuery("a=b&x=1");
assertEquals("b", result.get("a"));
assertEquals("1", result.get("x"));
link.setXHTML(false);
- result = link.parseQuery("true=false");
+ result = link.parseQuery("true=false&false=true&black=white");
assertEquals("false", result.get("true"));
+ assertEquals("true", result.get("false"));
+ assertEquals("white", result.get("black"));
}
public @Test void methodGetParams() throws Exception
@@ -581,9 +763,10 @@
Map result = link.getParams();
assertEquals("b", result.get("a"));
assertEquals("true", result.get("x"));
- result = link.param('y',false).getParams();
- assertEquals("b", result.get("a"));
- assertEquals("false", result.get("y"));
+ Map newresult = link.param('y',false).getParams();
+ assertFalse(result.equals(newresult));
+ assertEquals("b", newresult.get("a"));
+ assertEquals(Boolean.FALSE, newresult.get("y"));
}
public @Test void methodSetFragment_Object() throws Exception
@@ -595,7 +778,7 @@
assertEquals(null, link.toString());
link = newInstance("/foo#bar");
link.setFragment("woo gie");
- assertEquals("/foo#woo%20gie", link.toString());
+ assertEquals("/foo#woo+gie", link.toString());
}
public @Test void methodGetAnchor() throws Exception
@@ -624,7 +807,7 @@
assertEquals("a b", link.anchor(space).getAnchor());
link = newInstance("http://go.com#foo");
assertEquals("http://go.com#true", link.anchor(true).toString());
- assertEquals("http://go.com#a%20b", link.anchor(space).toString());
+ assertEquals("http://go.com#a+b", link.anchor(space).toString());
}
public @Test void methodGetSelf() throws Exception