001    /*
002     * Copyright 2003-2008 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     *
016     * You are receiving this code free of charge, which represents many hours of
017     * effort from other individuals and corporations.  As a responsible member 
018     * of the community, you are asked (but not required) to donate any 
019     * enhancements or improvements back to the community under a similar open 
020     * source license.  Thank you. -TMN
021     */
022    package groovyx.net.http;
023    
024    import java.net.MalformedURLException;
025    import java.net.URI;
026    import java.net.URISyntaxException;
027    import java.net.URL;
028    import java.util.ArrayList;
029    import java.util.HashMap;
030    import java.util.List;
031    import java.util.Map;
032    
033    import org.apache.http.NameValuePair;
034    import org.apache.http.client.utils.URLEncodedUtils;
035    import org.apache.http.message.BasicNameValuePair;
036    
037    /**
038     * This class implements a mutable URI.  All <code>set</code>, <code>add</code> 
039     * and <code>remove</code> methods affect this class' internal URI 
040     * representation.  All mutator methods support chaining, e.g.
041     * <pre>
042     * new URIBuilder("http://www.google.com/")
043     *   .setScheme( "https" )
044     *   .setPort( 443 )
045     *   .setPath( "some/path" )
046     *   .toString();
047     * </pre>
048     * A slightly more 'Groovy' version would be:
049     * <pre>
050     * new URIBuilder('http://www.google.com/').with {
051     *    scheme = 'https'
052     *    port = 443
053     *    path = 'some/path'
054     *    query = [p1:1, p2:'two']
055     * }.toString()
056     * </pre>
057     * @author <a href='mailto:tnichols@enernoc.com'>Tom Nichols</a>
058     */
059    public class URIBuilder {
060            protected URI base;
061            private final String ENC = "UTF-8"; 
062            
063            public URIBuilder( String url ) throws URISyntaxException {
064                    base = new URI(url);
065            }
066            
067            public URIBuilder( URL url ) throws URISyntaxException {
068                    this.base = url.toURI();
069            }
070            
071            public URIBuilder( URI url ) {
072                    this.base = url;
073            }
074            
075            /**
076             * Attempts to convert a URL or String to a URI.
077             * @param uri a {@link URI}, {@link URL} or any object that produces a 
078             *   parse-able URI string from its <code>toString()</code> result.
079             * @return a valid URI parsed from the given object
080             * @throws URISyntaxException
081             */
082            public static URI convertToURI( Object uri ) throws URISyntaxException {
083                    if ( uri instanceof URI ) ;
084                    else if ( uri instanceof URL ) uri = ((URL)uri).toURI();
085                    else uri = new URI( uri.toString() ); // assume any other object type produces a valid URI string
086                    return (URI)uri;
087            }
088            
089            /**
090             * AKA protocol 
091             * @throws URISyntaxException 
092             */
093            public URIBuilder setScheme( String scheme ) throws URISyntaxException {
094                    this.base = new URI( scheme, base.getUserInfo(), 
095                                    base.getHost(), base.getPort(), base.getPath(),
096                                    base.getQuery(), base.getFragment() );
097                    return this;
098            }
099            
100            public URIBuilder setPort( int port ) throws URISyntaxException {
101                    this.base = new URI( base.getScheme(), base.getUserInfo(), 
102                                    base.getHost(), port, base.getPath(),
103                                    base.getQuery(), base.getFragment() );
104                    return this;
105            }
106            
107            public URIBuilder setHost( String host ) throws URISyntaxException {
108                    this.base = new URI( base.getScheme(), base.getUserInfo(), 
109                                    host, base.getPort(), base.getPath(),
110                                    base.getQuery(), base.getFragment() );
111                    return this;
112            }
113            
114            public URIBuilder setPath( String path ) throws URISyntaxException {
115                    path = base.resolve( path ).getPath();
116                    this.base = new URI( base.getScheme(), base.getUserInfo(), 
117                                    base.getHost(), base.getPort(), path,
118                                    base.getQuery(), base.getFragment() );
119                    return this;
120            }
121            
122            /**
123             * Set the query portion of the URI
124             * @param params a Map of parameters that will be transformed into the query string
125             * @return
126             * @throws URISyntaxException
127             */
128            public URIBuilder setQuery( Map<String,?> params ) throws URISyntaxException {
129                    List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());
130                    for ( Map.Entry<String, ?> entry : params.entrySet() ) {
131                            String val = ( entry.getValue() != null ) ? 
132                                            entry.getValue().toString() : ""; 
133                            pairs.add( new BasicNameValuePair( 
134                                            entry.getKey(), val ) );
135                    }
136                    String queryString = URLEncodedUtils.format( pairs, ENC );
137                    this.base = new URI( base.getScheme(), base.getUserInfo(), 
138                                    base.getHost(), base.getPort(), base.getPath(),
139                                    queryString, base.getFragment() );
140                    return this;
141            }
142            
143            /**
144             * Get the query string as a map
145             * @return
146             */
147            public Map<String,String> getQuery() {
148                    Map<String,String> params = new HashMap<String, String>();          
149                    List<NameValuePair> pairs = URLEncodedUtils.parse( this.base, ENC );
150                    for ( NameValuePair pair : pairs ) 
151                            params.put( pair.getName(), pair.getValue() );
152                    return params;
153            }
154            
155            public boolean hasQueryParam( String name ) {
156                    return getQuery().get( name ) != null;
157            }
158            
159            public URIBuilder removeQueryParam( String param ) throws URISyntaxException {
160                    Map<String,String> params = getQuery();
161                    params.remove( param );
162                    this.setQuery( params );
163                    return this;
164            }
165            
166            /**
167             * This will append a param to the existing query string.  If the given 
168             * param is already part of the query string, it will be replaced.
169             * @param param
170             * @param value
171             * @throws URISyntaxException 
172             */
173            public URIBuilder addQueryParam( String param, Object value ) throws URISyntaxException {
174                    Map<String,String> params = getQuery();
175                    if ( value == null ) value = ""; 
176                    params.put( param, value.toString() );
177                    this.setQuery( params );
178                    return this;
179            }
180            
181            @SuppressWarnings("unchecked")
182            public URIBuilder addQueryParams( Map<String,?> params ) throws URISyntaxException {
183                    Map existing = this.getQuery();
184                    existing.putAll( params );
185                    this.setQuery( existing );
186                    return this;
187            }
188            
189            /**
190             * The document fragment, without a preceeding '#'
191             * @param fragment
192             * @throws URISyntaxException
193             */
194            public URIBuilder setFragment( String fragment ) throws URISyntaxException {
195                    this.base = new URI( base.getScheme(), base.getUserInfo(), 
196                                    base.getHost(), base.getPort(), base.getPath(),
197                                    base.getQuery(), fragment );
198                    return this;
199            }
200            
201            @Override public String toString() {
202                    return base.toString();
203            }
204            
205            public URL toURL() throws MalformedURLException {
206                    return base.toURL();
207            }
208            
209            public URI toURI() { return this.base; }
210    }