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 Charset charset;
63      private String fragment;
64      private String encodedFragment;
65  
66      /**
67       * Constructs an empty instance.
68       */
69      public URIBuilder() {
70          super();
71          this.port = -1;
72      }
73  
74      /**
75       * Construct an instance from the string which must be a valid URI.
76       *
77       * @param string a valid URI in string form
78       * @throws URISyntaxException if the input is not a valid URI
79       */
80      public URIBuilder(final String string) throws URISyntaxException {
81          super();
82          digestURI(new URI(string));
83      }
84  
85      /**
86       * Construct an instance from the provided URI.
87       * @param uri
88       */
89      public URIBuilder(final URI uri) {
90          super();
91          digestURI(uri);
92      }
93  
94      /**
95       * @since 4.4
96       */
97      public URIBuilder setCharset(final Charset charset) {
98          this.charset = charset;
99          return this;
100     }
101 
102     /**
103      * @since 4.4
104      */
105     public Charset getCharset() {
106         return charset;
107     }
108 
109     private List <NameValuePair> parseQuery(final String query, final Charset charset) {
110         if (query != null && !query.isEmpty()) {
111             return URLEncodedUtils.parse(query, charset);
112         }
113         return null;
114     }
115 
116     /**
117      * Builds a {@link URI} instance.
118      */
119     public URI build() throws URISyntaxException {
120         return new URI(buildString());
121     }
122 
123     private String buildString() {
124         final StringBuilder sb = new StringBuilder();
125         if (this.scheme != null) {
126             sb.append(this.scheme).append(':');
127         }
128         if (this.encodedSchemeSpecificPart != null) {
129             sb.append(this.encodedSchemeSpecificPart);
130         } else {
131             if (this.encodedAuthority != null) {
132                 sb.append("//").append(this.encodedAuthority);
133             } else if (this.host != null) {
134                 sb.append("//");
135                 if (this.encodedUserInfo != null) {
136                     sb.append(this.encodedUserInfo).append("@");
137                 } else if (this.userInfo != null) {
138                     sb.append(encodeUserInfo(this.userInfo)).append("@");
139                 }
140                 if (InetAddressUtils.isIPv6Address(this.host)) {
141                     sb.append("[").append(this.host).append("]");
142                 } else {
143                     sb.append(this.host);
144                 }
145                 if (this.port >= 0) {
146                     sb.append(":").append(this.port);
147                 }
148             }
149             if (this.encodedPath != null) {
150                 sb.append(normalizePath(this.encodedPath));
151             } else if (this.path != null) {
152                 sb.append(encodePath(normalizePath(this.path)));
153             }
154             if (this.encodedQuery != null) {
155                 sb.append("?").append(this.encodedQuery);
156             } else if (this.queryParams != null) {
157                 sb.append("?").append(encodeUrlForm(this.queryParams));
158             } else if (this.query != null) {
159                 sb.append("?").append(encodeUric(this.query));
160             }
161         }
162         if (this.encodedFragment != null) {
163             sb.append("#").append(this.encodedFragment);
164         } else if (this.fragment != null) {
165             sb.append("#").append(encodeUric(this.fragment));
166         }
167         return sb.toString();
168     }
169 
170     private void digestURI(final URI uri) {
171         this.scheme = uri.getScheme();
172         this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
173         this.encodedAuthority = uri.getRawAuthority();
174         this.host = uri.getHost();
175         this.port = uri.getPort();
176         this.encodedUserInfo = uri.getRawUserInfo();
177         this.userInfo = uri.getUserInfo();
178         this.encodedPath = uri.getRawPath();
179         this.path = uri.getPath();
180         this.encodedQuery = uri.getRawQuery();
181         this.queryParams = parseQuery(uri.getRawQuery(), this.charset != null ? this.charset : Consts.UTF_8);
182         this.encodedFragment = uri.getRawFragment();
183         this.fragment = uri.getFragment();
184     }
185 
186     private String encodeUserInfo(final String userInfo) {
187         return URLEncodedUtils.encUserInfo(userInfo, this.charset != null ? this.charset : Consts.UTF_8);
188     }
189 
190     private String encodePath(final String path) {
191         return URLEncodedUtils.encPath(path, this.charset != null ? this.charset : Consts.UTF_8);
192     }
193 
194     private String encodeUrlForm(final List<NameValuePair> params) {
195         return URLEncodedUtils.format(params, this.charset != null ? this.charset : Consts.UTF_8);
196     }
197 
198     private String encodeUric(final String fragment) {
199         return URLEncodedUtils.encUric(fragment, this.charset != null ? this.charset : Consts.UTF_8);
200     }
201 
202     /**
203      * Sets URI scheme.
204      */
205     public URIBuilder setScheme(final String scheme) {
206         this.scheme = scheme;
207         return this;
208     }
209 
210     /**
211      * Sets URI user info. The value is expected to be unescaped and may contain non ASCII
212      * characters.
213      */
214     public URIBuilder setUserInfo(final String userInfo) {
215         this.userInfo = userInfo;
216         this.encodedSchemeSpecificPart = null;
217         this.encodedAuthority = null;
218         this.encodedUserInfo = null;
219         return this;
220     }
221 
222     /**
223      * Sets URI user info as a combination of username and password. These values are expected to
224      * be unescaped and may contain non ASCII characters.
225      */
226     public URIBuilder setUserInfo(final String username, final String password) {
227         return setUserInfo(username + ':' + password);
228     }
229 
230     /**
231      * Sets URI host.
232      */
233     public URIBuilder setHost(final String host) {
234         this.host = host;
235         this.encodedSchemeSpecificPart = null;
236         this.encodedAuthority = null;
237         return this;
238     }
239 
240     /**
241      * Sets URI port.
242      */
243     public URIBuilder setPort(final int port) {
244         this.port = port < 0 ? -1 : port;
245         this.encodedSchemeSpecificPart = null;
246         this.encodedAuthority = null;
247         return this;
248     }
249 
250     /**
251      * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters.
252      */
253     public URIBuilder setPath(final String path) {
254         this.path = path;
255         this.encodedSchemeSpecificPart = null;
256         this.encodedPath = null;
257         return this;
258     }
259 
260     /**
261      * Removes URI query.
262      */
263     public URIBuilder removeQuery() {
264         this.queryParams = null;
265         this.query = null;
266         this.encodedQuery = null;
267         this.encodedSchemeSpecificPart = null;
268         return this;
269     }
270 
271     /**
272      * Sets URI query.
273      * <p>
274      * The value is expected to be encoded form data.
275      *
276      * @deprecated (4.3) use {@link #setParameters(List)} or {@link #setParameters(NameValuePair...)}
277      *
278      * @see URLEncodedUtils#parse
279      */
280     @Deprecated
281     public URIBuilder setQuery(final String query) {
282         this.queryParams = parseQuery(query, this.charset != null ? this.charset : Consts.UTF_8);
283         this.query = null;
284         this.encodedQuery = null;
285         this.encodedSchemeSpecificPart = null;
286         return this;
287     }
288 
289     /**
290      * Sets URI query parameters. The parameter name / values are expected to be unescaped
291      * and may contain non ASCII characters.
292      * <p/>
293      * Please note query parameters and custom query component are mutually exclusive. This method
294      * will remove custom query if present.
295      *
296      * @since 4.3
297      */
298     public URIBuilder setParameters(final List <NameValuePair> nvps) {
299         if (this.queryParams == null) {
300             this.queryParams = new ArrayList<NameValuePair>();
301         } else {
302             this.queryParams.clear();
303         }
304         this.queryParams.addAll(nvps);
305         this.encodedQuery = null;
306         this.encodedSchemeSpecificPart = null;
307         this.query = null;
308         return this;
309     }
310 
311     /**
312      * Adds URI query parameters. The parameter name / values are expected to be unescaped
313      * and may contain non ASCII characters.
314      * <p/>
315      * Please note query parameters and custom query component are mutually exclusive. This method
316      * will remove custom query if present.
317      *
318      * @since 4.3
319      */
320     public URIBuilder addParameters(final List <NameValuePair> nvps) {
321         if (this.queryParams == null) {
322             this.queryParams = new ArrayList<NameValuePair>();
323         }
324         this.queryParams.addAll(nvps);
325         this.encodedQuery = null;
326         this.encodedSchemeSpecificPart = null;
327         this.query = null;
328         return this;
329     }
330 
331     /**
332      * Sets URI query parameters. The parameter name / values are expected to be unescaped
333      * and may contain non ASCII characters.
334      * <p/>
335      * Please note query parameters and custom query component are mutually exclusive. This method
336      * will remove custom query if present.
337      *
338      * @since 4.3
339      */
340     public URIBuilder setParameters(final NameValuePair... nvps) {
341         if (this.queryParams == null) {
342             this.queryParams = new ArrayList<NameValuePair>();
343         } else {
344             this.queryParams.clear();
345         }
346         for (final NameValuePair nvp: nvps) {
347             this.queryParams.add(nvp);
348         }
349         this.encodedQuery = null;
350         this.encodedSchemeSpecificPart = null;
351         this.query = null;
352         return this;
353     }
354 
355     /**
356      * Adds parameter to URI query. The parameter name and value are expected to be unescaped
357      * and may contain non ASCII characters.
358      * <p/>
359      * Please note query parameters and custom query component are mutually exclusive. This method
360      * will remove custom query if present.
361      */
362     public URIBuilder addParameter(final String param, final String value) {
363         if (this.queryParams == null) {
364             this.queryParams = new ArrayList<NameValuePair>();
365         }
366         this.queryParams.add(new BasicNameValuePair(param, value));
367         this.encodedQuery = null;
368         this.encodedSchemeSpecificPart = null;
369         this.query = null;
370         return this;
371     }
372 
373     /**
374      * Sets parameter of URI query overriding existing value if set. The parameter name and value
375      * are expected to be unescaped and may contain non ASCII characters.
376      * <p/>
377      * Please note query parameters and custom query component are mutually exclusive. This method
378      * will remove custom query if present.
379      */
380     public URIBuilder setParameter(final String param, final String value) {
381         if (this.queryParams == null) {
382             this.queryParams = new ArrayList<NameValuePair>();
383         }
384         if (!this.queryParams.isEmpty()) {
385             for (final Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
386                 final NameValuePair nvp = it.next();
387                 if (nvp.getName().equals(param)) {
388                     it.remove();
389                 }
390             }
391         }
392         this.queryParams.add(new BasicNameValuePair(param, value));
393         this.encodedQuery = null;
394         this.encodedSchemeSpecificPart = null;
395         this.query = null;
396         return this;
397     }
398 
399     /**
400      * Clears URI query parameters.
401      *
402      * @since 4.3
403      */
404     public URIBuilder clearParameters() {
405         this.queryParams = null;
406         this.encodedQuery = null;
407         this.encodedSchemeSpecificPart = null;
408         return this;
409     }
410 
411     /**
412      * Sets custom URI query. The value is expected to be unescaped and may contain non ASCII
413      * characters.
414      * <p/>
415      * Please note query parameters and custom query component are mutually exclusive. This method
416      * will remove query parameters if present.
417      *
418      * @since 4.3
419      */
420     public URIBuilder setCustomQuery(final String query) {
421         this.query = query;
422         this.encodedQuery = null;
423         this.encodedSchemeSpecificPart = null;
424         this.queryParams = null;
425         return this;
426     }
427 
428     /**
429      * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII
430      * characters.
431      */
432     public URIBuilder setFragment(final String fragment) {
433         this.fragment = fragment;
434         this.encodedFragment = null;
435         return this;
436     }
437 
438     /**
439      * @since 4.3
440      */
441     public boolean isAbsolute() {
442         return this.scheme != null;
443     }
444 
445     /**
446      * @since 4.3
447      */
448     public boolean isOpaque() {
449         return this.path == null;
450     }
451 
452     public String getScheme() {
453         return this.scheme;
454     }
455 
456     public String getUserInfo() {
457         return this.userInfo;
458     }
459 
460     public String getHost() {
461         return this.host;
462     }
463 
464     public int getPort() {
465         return this.port;
466     }
467 
468     public String getPath() {
469         return this.path;
470     }
471 
472     public List<NameValuePair> getQueryParams() {
473         if (this.queryParams != null) {
474             return new ArrayList<NameValuePair>(this.queryParams);
475         } else {
476             return new ArrayList<NameValuePair>();
477         }
478     }
479 
480     public String getFragment() {
481         return this.fragment;
482     }
483 
484     @Override
485     public String toString() {
486         return buildString();
487     }
488 
489     private static String normalizePath(final String path) {
490         String s = path;
491         if (s == null) {
492             return null;
493         }
494         int n = 0;
495         for (; n < s.length(); n++) {
496             if (s.charAt(n) != '/') {
497                 break;
498             }
499         }
500         if (n > 1) {
501             s = s.substring(n - 1);
502         }
503         return s;
504     }
505 
506 }