View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.client.utils;
28  
29  import java.net.URI;
30  import java.net.URISyntaxException;
31  import java.nio.charset.Charset;
32  import java.util.ArrayList;
33  import java.util.Iterator;
34  import java.util.List;
35  
36  import org.apache.http.Consts;
37  import org.apache.http.NameValuePair;
38  import org.apache.http.annotation.NotThreadSafe;
39  import org.apache.http.conn.util.InetAddressUtils;
40  import org.apache.http.message.BasicNameValuePair;
41  
42  /**
43   * Builder for {@link URI} instances.
44   *
45   * @since 4.2
46   */
47  @NotThreadSafe
48  public class URIBuilder {
49  
50      private String scheme;
51      private String encodedSchemeSpecificPart;
52      private String encodedAuthority;
53      private String userInfo;
54      private String encodedUserInfo;
55      private String host;
56      private int port;
57      private String path;
58      private String encodedPath;
59      private String encodedQuery;
60      private List<NameValuePair> queryParams;
61      private String query;
62      private String fragment;
63      private String encodedFragment;
64  
65      /**
66       * Constructs an empty instance.
67       */
68      public URIBuilder() {
69          super();
70          this.port = -1;
71      }
72  
73      /**
74       * Construct an instance from the string which must be a valid URI.
75       *
76       * @param string a valid URI in string form
77       * @throws URISyntaxException if the input is not a valid URI
78       */
79      public URIBuilder(final String string) throws URISyntaxException {
80          super();
81          digestURI(new URI(string));
82      }
83  
84      /**
85       * Construct an instance from the provided URI.
86       * @param uri
87       */
88      public URIBuilder(final URI uri) {
89          super();
90          digestURI(uri);
91      }
92  
93      private List <NameValuePair> parseQuery(final String query, final Charset charset) {
94          if (query != null && query.length() > 0) {
95              return URLEncodedUtils.parse(query, charset);
96          }
97          return null;
98      }
99  
100     /**
101      * Builds a {@link URI} instance.
102      */
103     public URI build() throws URISyntaxException {
104         return new URI(buildString());
105     }
106 
107     private String buildString() {
108         final StringBuilder sb = new StringBuilder();
109         if (this.scheme != null) {
110             sb.append(this.scheme).append(':');
111         }
112         if (this.encodedSchemeSpecificPart != null) {
113             sb.append(this.encodedSchemeSpecificPart);
114         } else {
115             if (this.encodedAuthority != null) {
116                 sb.append("//").append(this.encodedAuthority);
117             } else if (this.host != null) {
118                 sb.append("//");
119                 if (this.encodedUserInfo != null) {
120                     sb.append(this.encodedUserInfo).append("@");
121                 } else if (this.userInfo != null) {
122                     sb.append(encodeUserInfo(this.userInfo)).append("@");
123                 }
124                 if (InetAddressUtils.isIPv6Address(this.host)) {
125                     sb.append("[").append(this.host).append("]");
126                 } else {
127                     sb.append(this.host);
128                 }
129                 if (this.port >= 0) {
130                     sb.append(":").append(this.port);
131                 }
132             }
133             if (this.encodedPath != null) {
134                 sb.append(normalizePath(this.encodedPath));
135             } else if (this.path != null) {
136                 sb.append(encodePath(normalizePath(this.path)));
137             }
138             if (this.encodedQuery != null) {
139                 sb.append("?").append(this.encodedQuery);
140             } else if (this.queryParams != null) {
141                 sb.append("?").append(encodeUrlForm(this.queryParams));
142             } else if (this.query != null) {
143                 sb.append("?").append(encodeUric(this.query));
144             }
145         }
146         if (this.encodedFragment != null) {
147             sb.append("#").append(this.encodedFragment);
148         } else if (this.fragment != null) {
149             sb.append("#").append(encodeUric(this.fragment));
150         }
151         return sb.toString();
152     }
153 
154     private void digestURI(final URI uri) {
155         this.scheme = uri.getScheme();
156         this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
157         this.encodedAuthority = uri.getRawAuthority();
158         this.host = uri.getHost();
159         this.port = uri.getPort();
160         this.encodedUserInfo = uri.getRawUserInfo();
161         this.userInfo = uri.getUserInfo();
162         this.encodedPath = uri.getRawPath();
163         this.path = uri.getPath();
164         this.encodedQuery = uri.getRawQuery();
165         this.queryParams = parseQuery(uri.getRawQuery(), Consts.UTF_8);
166         this.encodedFragment = uri.getRawFragment();
167         this.fragment = uri.getFragment();
168     }
169 
170     private String encodeUserInfo(final String userInfo) {
171         return URLEncodedUtils.encUserInfo(userInfo, Consts.UTF_8);
172     }
173 
174     private String encodePath(final String path) {
175         return URLEncodedUtils.encPath(path, Consts.UTF_8);
176     }
177 
178     private String encodeUrlForm(final List<NameValuePair> params) {
179         return URLEncodedUtils.format(params, Consts.UTF_8);
180     }
181 
182     private String encodeUric(final String fragment) {
183         return URLEncodedUtils.encUric(fragment, Consts.UTF_8);
184     }
185 
186     /**
187      * Sets URI scheme.
188      */
189     public URIBuilder setScheme(final String scheme) {
190         this.scheme = scheme;
191         return this;
192     }
193 
194     /**
195      * Sets URI user info. The value is expected to be unescaped and may contain non ASCII
196      * characters.
197      */
198     public URIBuilder setUserInfo(final String userInfo) {
199         this.userInfo = userInfo;
200         this.encodedSchemeSpecificPart = null;
201         this.encodedAuthority = null;
202         this.encodedUserInfo = null;
203         return this;
204     }
205 
206     /**
207      * Sets URI user info as a combination of username and password. These values are expected to
208      * be unescaped and may contain non ASCII characters.
209      */
210     public URIBuilder setUserInfo(final String username, final String password) {
211         return setUserInfo(username + ':' + password);
212     }
213 
214     /**
215      * Sets URI host.
216      */
217     public URIBuilder setHost(final String host) {
218         this.host = host;
219         this.encodedSchemeSpecificPart = null;
220         this.encodedAuthority = null;
221         return this;
222     }
223 
224     /**
225      * Sets URI port.
226      */
227     public URIBuilder setPort(final int port) {
228         this.port = port < 0 ? -1 : port;
229         this.encodedSchemeSpecificPart = null;
230         this.encodedAuthority = null;
231         return this;
232     }
233 
234     /**
235      * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters.
236      */
237     public URIBuilder setPath(final String path) {
238         this.path = path;
239         this.encodedSchemeSpecificPart = null;
240         this.encodedPath = null;
241         return this;
242     }
243 
244     /**
245      * Removes URI query.
246      */
247     public URIBuilder removeQuery() {
248         this.queryParams = null;
249         this.query = null;
250         this.encodedQuery = null;
251         this.encodedSchemeSpecificPart = null;
252         return this;
253     }
254 
255     /**
256      * Sets URI query.
257      * <p>
258      * The value is expected to be encoded form data.
259      *
260      * @deprecated (4.3) use {@link #setParameters(List)} or {@link #setParameters(NameValuePair...)}
261      *
262      * @see URLEncodedUtils#parse
263      */
264     @Deprecated
265     public URIBuilder setQuery(final String query) {
266         this.queryParams = parseQuery(query, Consts.UTF_8);
267         this.query = null;
268         this.encodedQuery = null;
269         this.encodedSchemeSpecificPart = null;
270         return this;
271     }
272 
273     /**
274      * Sets URI query parameters. The parameter name / values are expected to be unescaped
275      * and may contain non ASCII characters.
276      * <p/>
277      * Please note query parameters and custom query component are mutually exclusive. This method
278      * will remove custom query if present.
279      *
280      * @since 4.3
281      */
282     public URIBuilder setParameters(final List <NameValuePair> nvps) {
283         if (this.queryParams == null) {
284             this.queryParams = new ArrayList<NameValuePair>();
285         } else {
286             this.queryParams.clear();
287         }
288         this.queryParams.addAll(nvps);
289         this.encodedQuery = null;
290         this.encodedSchemeSpecificPart = null;
291         this.query = null;
292         return this;
293     }
294 
295     /**
296      * Adds URI query parameters. The parameter name / values are expected to be unescaped
297      * and may contain non ASCII characters.
298      * <p/>
299      * Please note query parameters and custom query component are mutually exclusive. This method
300      * will remove custom query if present.
301      *
302      * @since 4.3
303      */
304     public URIBuilder addParameters(final List <NameValuePair> nvps) {
305         if (this.queryParams == null) {
306             this.queryParams = new ArrayList<NameValuePair>();
307         }
308         this.queryParams.addAll(nvps);
309         this.encodedQuery = null;
310         this.encodedSchemeSpecificPart = null;
311         this.query = null;
312         return this;
313     }
314 
315     /**
316      * Sets URI query parameters. The parameter name / values are expected to be unescaped
317      * and may contain non ASCII characters.
318      * <p/>
319      * Please note query parameters and custom query component are mutually exclusive. This method
320      * will remove custom query if present.
321      *
322      * @since 4.3
323      */
324     public URIBuilder setParameters(final NameValuePair... nvps) {
325         if (this.queryParams == null) {
326             this.queryParams = new ArrayList<NameValuePair>();
327         } else {
328             this.queryParams.clear();
329         }
330         for (final NameValuePair nvp: nvps) {
331             this.queryParams.add(nvp);
332         }
333         this.encodedQuery = null;
334         this.encodedSchemeSpecificPart = null;
335         this.query = null;
336         return this;
337     }
338 
339     /**
340      * Adds parameter to URI query. The parameter name and value are expected to be unescaped
341      * and may contain non ASCII characters.
342      * <p/>
343      * Please note query parameters and custom query component are mutually exclusive. This method
344      * will remove custom query if present.
345      */
346     public URIBuilder addParameter(final String param, final String value) {
347         if (this.queryParams == null) {
348             this.queryParams = new ArrayList<NameValuePair>();
349         }
350         this.queryParams.add(new BasicNameValuePair(param, value));
351         this.encodedQuery = null;
352         this.encodedSchemeSpecificPart = null;
353         this.query = null;
354         return this;
355     }
356 
357     /**
358      * Sets parameter of URI query overriding existing value if set. The parameter name and value
359      * are expected to be unescaped and may contain non ASCII characters.
360      * <p/>
361      * Please note query parameters and custom query component are mutually exclusive. This method
362      * will remove custom query if present.
363      */
364     public URIBuilder setParameter(final String param, final String value) {
365         if (this.queryParams == null) {
366             this.queryParams = new ArrayList<NameValuePair>();
367         }
368         if (!this.queryParams.isEmpty()) {
369             for (final Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
370                 final NameValuePair nvp = it.next();
371                 if (nvp.getName().equals(param)) {
372                     it.remove();
373                 }
374             }
375         }
376         this.queryParams.add(new BasicNameValuePair(param, value));
377         this.encodedQuery = null;
378         this.encodedSchemeSpecificPart = null;
379         this.query = null;
380         return this;
381     }
382 
383     /**
384      * Clears URI query parameters.
385      *
386      * @since 4.3
387      */
388     public URIBuilder clearParameters() {
389         this.queryParams = null;
390         this.encodedQuery = null;
391         this.encodedSchemeSpecificPart = null;
392         return this;
393     }
394 
395     /**
396      * Sets custom URI query. The value is expected to be unescaped and may contain non ASCII
397      * characters.
398      * <p/>
399      * Please note query parameters and custom query component are mutually exclusive. This method
400      * will remove query parameters if present.
401      *
402      * @since 4.3
403      */
404     public URIBuilder setCustomQuery(final String query) {
405         this.query = query;
406         this.encodedQuery = null;
407         this.encodedSchemeSpecificPart = null;
408         this.queryParams = null;
409         return this;
410     }
411 
412     /**
413      * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII
414      * characters.
415      */
416     public URIBuilder setFragment(final String fragment) {
417         this.fragment = fragment;
418         this.encodedFragment = null;
419         return this;
420     }
421 
422     /**
423      * @since 4.3
424      */
425     public boolean isAbsolute() {
426         return this.scheme != null;
427     }
428 
429     /**
430      * @since 4.3
431      */
432     public boolean isOpaque() {
433         return this.path == null;
434     }
435 
436     public String getScheme() {
437         return this.scheme;
438     }
439 
440     public String getUserInfo() {
441         return this.userInfo;
442     }
443 
444     public String getHost() {
445         return this.host;
446     }
447 
448     public int getPort() {
449         return this.port;
450     }
451 
452     public String getPath() {
453         return this.path;
454     }
455 
456     public List<NameValuePair> getQueryParams() {
457         if (this.queryParams != null) {
458             return new ArrayList<NameValuePair>(this.queryParams);
459         } else {
460             return new ArrayList<NameValuePair>();
461         }
462     }
463 
464     public String getFragment() {
465         return this.fragment;
466     }
467 
468     @Override
469     public String toString() {
470         return buildString();
471     }
472 
473     private static String normalizePath(final String path) {
474         String s = path;
475         if (s == null) {
476             return null;
477         }
478         int n = 0;
479         for (; n < s.length(); n++) {
480             if (s.charAt(n) != '/') {
481                 break;
482             }
483         }
484         if (n > 1) {
485             s = s.substring(n - 1);
486         }
487         return s;
488     }
489 
490 }