groovyx.net.http.URIBuilder.java Source code

Java tutorial

Introduction

Here is the source code for groovyx.net.http.URIBuilder.java

Source

/*
 * Copyright 2008-2011 Thomas Nichols.  http://blog.thomnichols.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * You are receiving this code free of charge, which represents many hours of
 * effort from other individuals and corporations.  As a responsible member
 * of the community, you are encouraged (but not required) to donate any
 * enhancements or improvements back to the community under a similar open
 * source license.  Thank you. -TMN
 */
package groovyx.net.http;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;

/**
 * This class implements a mutable URI.  All <code>set</code>, <code>add</code>
 * and <code>remove</code> methods affect this class' internal URI
 * representation.  All mutator methods support chaining, e.g.
 * <pre>
 * new URIBuilder("http://www.google.com/")
 *   .setScheme( "https" )
 *   .setPort( 443 )
 *   .setPath( "some/path" )
 *   .toString();
 * </pre>
 * A slightly more 'Groovy' version would be:
 * <pre>
 * new URIBuilder('http://www.google.com/').with {
 *    scheme = 'https'
 *    port = 443
 *    path = 'some/path'
 *    query = [p1:1, p2:'two']
 * }.toString()
 * </pre>
 * @author <a href='mailto:tomstrummer+httpbuilder@gmail.com'>Tom Nichols</a>
 */
public class URIBuilder implements Cloneable {
    protected URI base;
    private final String ENC = "UTF-8";

    public URIBuilder(String url) throws URISyntaxException {
        base = new URI(url);
    }

    public URIBuilder(URL url) throws URISyntaxException {
        this.base = url.toURI();
    }

    /**
     * @throws IllegalArgumentException if uri is null
     * @param uri
     */
    public URIBuilder(URI uri) throws IllegalArgumentException {
        if (uri == null)
            throw new IllegalArgumentException("uri cannot be null");
        this.base = uri;
    }

    /**
     * Utility method to convert a number of type to a URI instance.
     * @param uri a {@link URI}, {@link URL} or any object that produces a
     *   valid URI string from its <code>toString()</code> result.
     * @return a valid URI parsed from the given object
     * @throws URISyntaxException
     */
    public static URI convertToURI(Object uri) throws URISyntaxException {
        if (uri instanceof URI)
            return (URI) uri;
        if (uri instanceof URL)
            return ((URL) uri).toURI();
        if (uri instanceof URIBuilder)
            return ((URIBuilder) uri).toURI();
        return new URI(uri.toString()); // assume any other object type produces a valid URI string
    }

    protected URI update(String scheme, String userInfo, String host, int port, String path, String query,
            String fragment) throws URISyntaxException {
        URI u = new URI(scheme, userInfo, host, port, base.getPath(), null, null);

        StringBuilder sb = new StringBuilder();
        if (path != null)
            sb.append(path);
        if (query != null)
            sb.append('?').append(query);
        if (fragment != null)
            sb.append('#').append(fragment);
        return u.resolve(sb.toString());
    }

    /**
     * Set the URI scheme, AKA the 'protocol.'  e.g.
     * <code>setScheme('https')</code>
     * @throws URISyntaxException if the given scheme contains illegal characters.
     */
    public URIBuilder setScheme(String scheme) throws URISyntaxException {
        this.base = update(scheme, base.getUserInfo(), base.getHost(), base.getPort(), base.getRawPath(),
                base.getRawQuery(), base.getRawFragment());
        return this;
    }

    /**
     * Get the scheme for this URI.  See {@link URI#getScheme()}
     * @return the scheme portion of the URI
     */
    public String getScheme() {
        return this.base.getScheme();
    }

    /**
     * Set the port for this URI, or <code>-1</code> to unset the port.
     * @param port
     * @return this URIBuilder instance
     * @throws URISyntaxException
     */
    public URIBuilder setPort(int port) throws URISyntaxException {
        this.base = update(base.getScheme(), base.getUserInfo(), base.getHost(), port, base.getRawPath(),
                base.getRawQuery(), base.getRawFragment());
        return this;
    }

    /**
     * See {@link URI#getPort()}
     * @return the port portion of this URI (-1 if a port is not specified.)
     */
    public int getPort() {
        return this.base.getPort();
    }

    /**
     * Set the host portion of this URI.
     * @param host
     * @return this URIBuilder instance
     * @throws URISyntaxException if the host parameter contains illegal characters.
     */
    public URIBuilder setHost(String host) throws URISyntaxException {
        this.base = update(base.getScheme(), base.getUserInfo(), host, base.getPort(), base.getRawPath(),
                base.getRawQuery(), base.getRawFragment());
        return this;
    }

    /**
     * See {@link URI#getHost()}
     * @return the host portion of the URI
     */
    public String getHost() {
        return base.getHost();
    }

    /**
     * Set the path component of this URI.  The value may be absolute or
     * relative to the current path.
     * e.g. <pre>
     *   def uri = new URIBuilder( 'http://localhost/p1/p2?a=1' )
     *
     *   uri.path = '/p3/p2'
     *   assert uri.toString() == 'http://localhost/p3/p2?a=1'
     *
     *   uri.path = 'p2a'
     *   assert uri.toString() == 'http://localhost/p3/p2a?a=1'
     *
     *   uri.path = '../p4'
     *   assert uri.toString() == 'http://localhost/p4?a=1&b=2&c=3#frag'
     * <pre>
     * @param path the path portion of this URI, relative to the current URI.
     * @return this URIBuilder instance, for method chaining.
     * @throws URISyntaxException if the given path contains characters that
     *   cannot be converted to a valid URI
     */
    public URIBuilder setPath(String path) throws URISyntaxException {
        this.base = update(base.getScheme(), base.getUserInfo(), base.getHost(), base.getPort(),
                new URI(null, null, path, null, null).getRawPath(), base.getRawQuery(), base.getRawFragment());
        return this;
    }

    /**
     * Note that this property is <strong>not</strong> necessarily reflexive
     * with the {@link #setPath(String)} method!  <code>URIBuilder.setPath()</code>
     * will resolve a relative path, whereas this method will always return the
     * full, absolute path.
     * See {@link URI#getPath()}
     * @return the full path portion of the URI.
     */
    public String getPath() {
        return this.base.getPath();
    }

    /* TODO null/ zero-size check if this is ever made public */
    protected URIBuilder setQueryNVP(List<NameValuePair> nvp) throws URISyntaxException {
        /* Passing the query string in the URI constructor will
         * double-escape query parameters and goober things up.  So we have
         * to create a full path+query+fragment and use URI#resolve() to
         * create the new URI.  */
        StringBuilder sb = new StringBuilder();
        String path = base.getRawPath();
        if (path != null)
            sb.append(path);
        sb.append('?');
        sb.append(URLEncodedUtils.format(nvp, ENC));
        String frag = base.getRawFragment();
        if (frag != null)
            sb.append('#').append(frag);
        this.base = base.resolve(sb.toString());

        return this;
    }

    /**
     * Set the query portion of the URI.  For query parameters with multiple
     * values, put the values in a list like so:
     * <pre>uri.query = [ p1:'val1', p2:['val2', 'val3'] ]
     * // will produce a query string of ?p1=val1&p2=val2&p2=val3</pre>
     *
     * @param params a Map of parameters that will be transformed into the query string
     * @return this URIBuilder instance, for method chaining.
     * @throws URISyntaxException
     */
    public URIBuilder setQuery(Map<?, ?> params) throws URISyntaxException {
        if (params == null || params.size() < 1) {
            this.base = new URI(base.getScheme(), base.getUserInfo(), base.getHost(), base.getPort(),
                    base.getPath(), null, base.getFragment());
        } else {
            List<NameValuePair> nvp = new ArrayList<NameValuePair>(params.size());
            for (Object key : params.keySet()) {
                Object value = params.get(key);
                if (value instanceof List<?>) {
                    for (Object val : (List<?>) value)
                        nvp.add(new BasicNameValuePair(key.toString(), (val != null) ? val.toString() : ""));
                } else
                    nvp.add(new BasicNameValuePair(key.toString(), (value != null) ? value.toString() : ""));
            }
            this.setQueryNVP(nvp);
        }
        return this;
    }

    /**
     * Set the raw, already-escaped query string.  No additional escaping will
     * be done on the string.
     * @param query
     * @return
     */
    public URIBuilder setRawQuery(String query) throws URISyntaxException {
        this.base = update(base.getScheme(), base.getUserInfo(), base.getHost(), base.getPort(), base.getRawPath(),
                query, base.getRawFragment());
        return this;
    }

    /**
     * Get the query string as a map for convenience.  If any parameter contains
     * multiple values (e.g. <code>p1=one&p1=two</code>) both values will be
     * inserted into a list for that paramter key (<code>[p1 : ['one','two']]
     * </code>).  Note that this is not a "live" map.  Therefore, you cannot
     * call
     * <pre> uri.query.a = 'BCD'</pre>
     * You will not modify the query string but instead the generated map of
     * parameters.  Instead, you need to use {@link #removeQueryParam(String)}
     * first, then {@link #addQueryParam(String, Object)}, or call
     * {@link #setQuery(Map)} which will set the entire query string.
     * @return a map of String name/value pairs representing the URI's query
     * string.
     */
    public Map<String, Object> getQuery() {
        Map<String, Object> params = new HashMap<String, Object>();
        List<NameValuePair> pairs = this.getQueryNVP();
        if (pairs == null)
            return null;

        for (NameValuePair pair : pairs) {

            String key = pair.getName();
            Object existing = params.get(key);

            if (existing == null)
                params.put(key, pair.getValue());

            else if (existing instanceof List<?>)
                ((List) existing).add(pair.getValue());

            else {
                List<String> vals = new ArrayList<String>(2);
                vals.add((String) existing);
                vals.add(pair.getValue());
                params.put(key, vals);
            }
        }

        return params;
    }

    protected List<NameValuePair> getQueryNVP() {
        if (this.base.getQuery() == null)
            return null;
        List<NameValuePair> nvps = URLEncodedUtils.parse(this.base, ENC);
        List<NameValuePair> newList = new ArrayList<NameValuePair>();
        if (nvps != null)
            newList.addAll(nvps);
        return newList;
    }

    /**
     * Indicates if the given parameter is already part of this URI's query
     * string.
     * @param name the query parameter name
     * @return true if the given parameter name is found in the query string of
     *    the URI.
     */
    public boolean hasQueryParam(String name) {
        return getQuery().get(name) != null;
    }

    /**
     * Remove the given query parameter from this URI's query string.
     * @param param the query name to remove
     * @return this URIBuilder instance, for method chaining.
     * @throws URISyntaxException
     */
    public URIBuilder removeQueryParam(String param) throws URISyntaxException {
        List<NameValuePair> params = getQueryNVP();
        NameValuePair found = null;
        for (NameValuePair nvp : params) // BOO linear search.  Assume the list is small.
            if (nvp.getName().equals(param)) {
                found = nvp;
                break;
            }

        if (found == null)
            throw new IllegalArgumentException("Param '" + param + "' not found");
        params.remove(found);
        this.setQueryNVP(params);
        return this;
    }

    protected URIBuilder addQueryParam(NameValuePair nvp) throws URISyntaxException {
        List<NameValuePair> params = getQueryNVP();
        if (params == null)
            params = new ArrayList<NameValuePair>();
        params.add(nvp);
        this.setQueryNVP(params);
        return this;
    }

    /**
     * This will append a query parameter to the existing query string.  If the given
     * parameter is already part of the query string, it will be appended to.
     * To replace the existing value of a certain parameter, either call
     * {@link #removeQueryParam(String)} first, or use {@link #getQuery()},
     * modify the value in the map, then call {@link #setQuery(Map)}.
     * @param param query parameter name
     * @param value query parameter value (will be converted to a string if
     *   not null.  If <code>value</code> is null, it will be set as the empty
     *   string.
     * @return this URIBuilder instance, for method chaining.
     * @throws URISyntaxException if the query parameter values cannot be
     * converted to a valid URI.
     * @see #setQuery(Map)
     */
    public URIBuilder addQueryParam(String param, Object value) throws URISyntaxException {
        this.addQueryParam(new BasicNameValuePair(param, (value != null) ? value.toString() : ""));
        return this;
    }

    protected URIBuilder addQueryParams(List<NameValuePair> nvp) throws URISyntaxException {
        List<NameValuePair> params = getQueryNVP();
        if (params == null)
            params = new ArrayList<NameValuePair>();
        params.addAll(nvp);
        this.setQueryNVP(params);
        return this;
    }

    /**
     * Add these parameters to the URIBuilder's existing query string.
     * Parameters may be passed either as a single map argument, or as a list
     * of named arguments.  e.g.
     * <pre> uriBuilder.addQueryParams( [one:1,two:2] )
     * uriBuilder.addQueryParams( three : 3 ) </pre>
     *
     * If any of the parameters already exist in the URI query, these values
     * will <strong>not</strong> replace them.  Multiple values for the same
     * query parameter may be added by putting them in a list. See
     * {@link #setQuery(Map)}.
     *
     * @param params parameters to add to the existing URI query (if any).
     * @return this URIBuilder instance, for method chaining.
     * @throws URISyntaxException
     */
    @SuppressWarnings("unchecked")
    public URIBuilder addQueryParams(Map<?, ?> params) throws URISyntaxException {
        List<NameValuePair> nvp = new ArrayList<NameValuePair>();
        for (Object key : params.keySet()) {
            Object value = params.get(key);
            if (value instanceof List) {
                for (Object val : (List) value)
                    nvp.add(new BasicNameValuePair(key.toString(), (val != null) ? val.toString() : ""));
            } else
                nvp.add(new BasicNameValuePair(key.toString(), (value != null) ? value.toString() : ""));
        }
        this.addQueryParams(nvp);
        return this;
    }

    /**
     * The document fragment, without a preceeding '#'.  Use <code>null</code>
     * to use no document fragment.
     * @param fragment
     * @return this URIBuilder instance, for method chaining.
     * @throws URISyntaxException if the given value contains illegal characters.
     */
    public URIBuilder setFragment(String fragment) throws URISyntaxException {
        this.base = update(base.getScheme(), base.getUserInfo(), base.getHost(), base.getPort(), base.getRawPath(),
                base.getRawQuery(), new URI(null, null, null, fragment).getRawFragment());
        return this;
    }

    /**
     * See {@link URI#getFragment()}
     * @return the URI document fragment
     */
    public String getFragment() {
        return this.base.getFragment();
    }

    /**
     * Set the userInfo portion of the URI, or <code>null</code> if the URI
     * should have no user information.
     * @param userInfo
     * @return this URIBuilder instance
     * @throws URISyntaxException if the given value contains illegal characters.
     */
    public URIBuilder setUserInfo(String userInfo) throws URISyntaxException {
        this.base = update(base.getScheme(), userInfo, base.getHost(), base.getPort(), base.getRawPath(),
                base.getRawQuery(), base.getRawFragment());

        return this;
    }

    /**
     * See {@link URI#getUserInfo()}
     * @return the user info portion of the URI, or <code>null</code> if it
     * is not specified.
     */
    public String getUserInfo() {
        return this.base.getUserInfo();
    }

    /**
     * Print this builder's URI representation.
     */
    @Override
    public String toString() {
        return base.toString();
    }

    /**
     * Convenience method to convert this object to a URL instance.
     * @return this builder as a URL
     * @throws MalformedURLException if the underlying URI does not represent a
     * valid URL.
     */
    public URL toURL() throws MalformedURLException {
        return base.toURL();
    }

    /**
     * Convenience method to convert this object to a URI instance.
     * @return this builder's underlying URI representation
     */
    public URI toURI() {
        return this.base;
    }

    /**
     * Implementation of Groovy's <code>as</code> operator, to allow type
     * conversion.
     * @param type <code>URL</code>, <code>URL</code>, or <code>String</code>.
     * @return a representation of this URIBuilder instance in the given type
     * @throws MalformedURLException if <code>type</code> is URL and this
     * URIBuilder instance does not represent a valid URL.
     */
    public Object asType(Class<?> type) throws MalformedURLException {
        if (type == URI.class)
            return this.toURI();
        if (type == URL.class)
            return this.toURL();
        if (type == String.class)
            return this.toString();
        throw new ClassCastException("Cannot cast instance of URIBuilder to class " + type);
    }

    /**
     * Create a copy of this URIBuilder instance.
     */
    @Override
    protected URIBuilder clone() {
        return new URIBuilder(this.base);
    }

    /**
     * Determine if this URIBuilder is equal to another URIBuilder instance.
     * @see URI#equals(Object)
     * @return if <code>obj</code> is a URIBuilder instance whose underlying
     *   URI implementation is equal to this one's.
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof URIBuilder))
            return false;
        return this.base.equals(((URIBuilder) obj).toURI());
    }
}