1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.core5.net;
28
29 import java.net.InetAddress;
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.net.UnknownHostException;
33 import java.nio.charset.Charset;
34 import java.nio.charset.StandardCharsets;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.BitSet;
38 import java.util.Collections;
39 import java.util.LinkedList;
40 import java.util.List;
41
42 import org.apache.hc.core5.http.HttpHost;
43 import org.apache.hc.core5.http.NameValuePair;
44 import org.apache.hc.core5.http.URIScheme;
45 import org.apache.hc.core5.http.message.BasicNameValuePair;
46 import org.apache.hc.core5.http.message.ParserCursor;
47 import org.apache.hc.core5.util.Args;
48 import org.apache.hc.core5.util.TextUtils;
49 import org.apache.hc.core5.util.Tokenizer;
50
51
52
53
54
55
56 public class URIBuilder {
57
58
59
60
61
62
63
64 public static URIBuilder localhost() throws UnknownHostException {
65 return new URIBuilder().setHost(InetAddress.getLocalHost());
66 }
67
68
69
70
71 public static URIBuilder loopbackAddress() {
72 return new URIBuilder().setHost(InetAddress.getLoopbackAddress());
73 }
74
75 private String scheme;
76 private String encodedSchemeSpecificPart;
77 private String encodedAuthority;
78 private String userInfo;
79 private String encodedUserInfo;
80 private String host;
81 private int port;
82 private String encodedPath;
83 private boolean pathRootless;
84 private List<String> pathSegments;
85 private String encodedQuery;
86 private List<NameValuePair> queryParams;
87 private String query;
88 private Charset charset;
89 private String fragment;
90 private String encodedFragment;
91
92
93
94
95 public URIBuilder() {
96 super();
97 this.port = -1;
98 }
99
100
101
102
103
104
105
106 public URIBuilder(final String uriString) throws URISyntaxException {
107 this(new URI(uriString), StandardCharsets.UTF_8);
108 }
109
110
111
112
113
114 public URIBuilder(final URI uri) {
115 this(uri, StandardCharsets.UTF_8);
116 }
117
118
119
120
121
122
123
124 public URIBuilder(final String uriString, final Charset charset) throws URISyntaxException {
125 this(new URI(uriString), charset);
126 }
127
128
129
130
131
132
133 public URIBuilder(final URI uri, final Charset charset) {
134 super();
135 digestURI(uri, charset);
136 }
137
138
139
140
141
142
143
144
145 public URIBuilder setAuthority(final NamedEndpoint authority) {
146 setUserInfo(null);
147 setHost(authority.getHostName());
148 setPort(authority.getPort());
149 return this;
150 }
151
152
153
154
155
156
157
158
159 public URIBuilder setAuthority(final URIAuthority authority) {
160 setUserInfo(authority.getUserInfo());
161 setHost(authority.getHostName());
162 setPort(authority.getPort());
163 return this;
164 }
165
166
167
168
169
170
171
172 public URIBuilder setCharset(final Charset charset) {
173 this.charset = charset;
174 return this;
175 }
176
177
178
179
180
181
182
183 public URIAuthority getAuthority() {
184 return new URIAuthority(getUserInfo(), getHost(), getPort());
185 }
186
187
188
189
190
191
192 public Charset getCharset() {
193 return charset;
194 }
195
196 private static final char QUERY_PARAM_SEPARATOR = '&';
197 private static final char PARAM_VALUE_SEPARATOR = '=';
198 private static final char PATH_SEPARATOR = '/';
199
200 private static final BitSet QUERY_PARAM_SEPARATORS = new BitSet(256);
201 private static final BitSet QUERY_VALUE_SEPARATORS = new BitSet(256);
202 private static final BitSet PATH_SEPARATORS = new BitSet(256);
203
204 static {
205 QUERY_PARAM_SEPARATORS.set(QUERY_PARAM_SEPARATOR);
206 QUERY_PARAM_SEPARATORS.set(PARAM_VALUE_SEPARATOR);
207 QUERY_VALUE_SEPARATORS.set(QUERY_PARAM_SEPARATOR);
208 PATH_SEPARATORS.set(PATH_SEPARATOR);
209 }
210
211 static List<NameValuePair> parseQuery(final CharSequence s, final Charset charset, final boolean plusAsBlank) {
212 if (s == null) {
213 return null;
214 }
215 final Tokenizer tokenParser = Tokenizer.INSTANCE;
216 final ParserCursor cursor = new ParserCursor(0, s.length());
217 final List<NameValuePair> list = new ArrayList<>();
218 while (!cursor.atEnd()) {
219 final String name = tokenParser.parseToken(s, cursor, QUERY_PARAM_SEPARATORS);
220 String value = null;
221 if (!cursor.atEnd()) {
222 final int delim = s.charAt(cursor.getPos());
223 cursor.updatePos(cursor.getPos() + 1);
224 if (delim == PARAM_VALUE_SEPARATOR) {
225 value = tokenParser.parseToken(s, cursor, QUERY_VALUE_SEPARATORS);
226 if (!cursor.atEnd()) {
227 cursor.updatePos(cursor.getPos() + 1);
228 }
229 }
230 }
231 if (!name.isEmpty()) {
232 list.add(new BasicNameValuePair(
233 PercentCodec.decode(name, charset, plusAsBlank),
234 PercentCodec.decode(value, charset, plusAsBlank)));
235 }
236 }
237 return list;
238 }
239
240 static List<String> splitPath(final CharSequence s) {
241 if (s == null) {
242 return Collections.emptyList();
243 }
244 final ParserCursor cursor = new ParserCursor(0, s.length());
245
246 if (cursor.atEnd()) {
247 return new ArrayList<>(0);
248 }
249 if (PATH_SEPARATORS.get(s.charAt(cursor.getPos()))) {
250 cursor.updatePos(cursor.getPos() + 1);
251 }
252 final List<String> list = new ArrayList<>();
253 final StringBuilder buf = new StringBuilder();
254 for (;;) {
255 if (cursor.atEnd()) {
256 list.add(buf.toString());
257 break;
258 }
259 final char current = s.charAt(cursor.getPos());
260 if (PATH_SEPARATORS.get(current)) {
261 list.add(buf.toString());
262 buf.setLength(0);
263 } else {
264 buf.append(current);
265 }
266 cursor.updatePos(cursor.getPos() + 1);
267 }
268 return list;
269 }
270
271 static List<String> parsePath(final CharSequence s, final Charset charset) {
272 if (s == null) {
273 return Collections.emptyList();
274 }
275 final List<String> segments = splitPath(s);
276 final List<String> list = new ArrayList<>(segments.size());
277 for (final String segment: segments) {
278 list.add(PercentCodec.decode(segment, charset));
279 }
280 return list;
281 }
282
283 static void formatPath(final StringBuilder buf, final Iterable<String> segments, final boolean rootless, final Charset charset) {
284 int i = 0;
285 for (final String segment : segments) {
286 if (i > 0 || !rootless) {
287 buf.append(PATH_SEPARATOR);
288 }
289 PercentCodec.encode(buf, segment, charset);
290 i++;
291 }
292 }
293
294 static void formatQuery(final StringBuilder buf, final Iterable<? extends NameValuePair> params, final Charset charset,
295 final boolean blankAsPlus) {
296 int i = 0;
297 for (final NameValuePair parameter : params) {
298 if (i > 0) {
299 buf.append(QUERY_PARAM_SEPARATOR);
300 }
301 PercentCodec.encode(buf, parameter.getName(), charset, blankAsPlus);
302 if (parameter.getValue() != null) {
303 buf.append(PARAM_VALUE_SEPARATOR);
304 PercentCodec.encode(buf, parameter.getValue(), charset, blankAsPlus);
305 }
306 i++;
307 }
308 }
309
310
311
312
313 public URI build() throws URISyntaxException {
314 if ((URIScheme.HTTPS.same(scheme) || URIScheme.HTTP.same(scheme)) && (TextUtils.isBlank(host))) {
315 throw new URISyntaxException(scheme, "http/https URI cannot have an empty host identifier");
316 }
317 return new URI(buildString());
318 }
319
320 private String buildString() {
321 final StringBuilder sb = new StringBuilder();
322 if (this.scheme != null) {
323 sb.append(this.scheme).append(':');
324 }
325 if (this.encodedSchemeSpecificPart != null) {
326 sb.append(this.encodedSchemeSpecificPart);
327 } else {
328 final boolean authoritySpecified;
329 if (this.encodedAuthority != null) {
330 sb.append("//").append(this.encodedAuthority);
331 authoritySpecified = true;
332 } else if (this.host != null) {
333 sb.append("//");
334 if (this.encodedUserInfo != null) {
335 sb.append(this.encodedUserInfo).append("@");
336 } else if (this.userInfo != null) {
337 final int idx = this.userInfo.indexOf(':');
338 if (idx != -1) {
339 PercentCodec.encode(sb, this.userInfo.substring(0, idx), this.charset);
340 sb.append(':');
341 PercentCodec.encode(sb, this.userInfo.substring(idx + 1), this.charset);
342 } else {
343 PercentCodec.encode(sb, this.userInfo, this.charset);
344 }
345 sb.append("@");
346 }
347 if (InetAddressUtils.isIPv6Address(this.host)) {
348 sb.append("[").append(this.host).append("]");
349 } else {
350 sb.append(PercentCodec.encode(this.host, this.charset));
351 }
352 if (this.port >= 0) {
353 sb.append(":").append(this.port);
354 }
355 authoritySpecified = true;
356 } else {
357 authoritySpecified = false;
358 }
359 if (this.encodedPath != null) {
360 if (authoritySpecified && !TextUtils.isEmpty(this.encodedPath) && !this.encodedPath.startsWith("/")) {
361 sb.append('/');
362 }
363 sb.append(this.encodedPath);
364 } else if (this.pathSegments != null) {
365 formatPath(sb, this.pathSegments, !authoritySpecified && this.pathRootless, this.charset);
366 }
367 if (this.encodedQuery != null) {
368 sb.append("?").append(this.encodedQuery);
369 } else if (this.queryParams != null && !this.queryParams.isEmpty()) {
370 sb.append("?");
371 formatQuery(sb, this.queryParams, this.charset, false);
372 } else if (this.query != null) {
373 sb.append("?");
374 PercentCodec.encode(sb, this.query, this.charset, PercentCodec.URIC, false);
375 }
376 }
377 if (this.encodedFragment != null) {
378 sb.append("#").append(this.encodedFragment);
379 } else if (this.fragment != null) {
380 sb.append("#");
381 PercentCodec.encode(sb, this.fragment, this.charset);
382 }
383 return sb.toString();
384 }
385
386 private void digestURI(final URI uri, final Charset charset) {
387 this.scheme = uri.getScheme();
388 this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
389 this.encodedAuthority = uri.getRawAuthority();
390 final String uriHost = uri.getHost();
391
392
393 this.host = uriHost != null && InetAddressUtils.isIPv6URLBracketedAddress(uriHost)
394 ? uriHost.substring(1, uriHost.length() - 1)
395 : uriHost;
396 this.port = uri.getPort();
397 this.encodedUserInfo = uri.getRawUserInfo();
398 this.userInfo = uri.getUserInfo();
399 if (this.encodedAuthority != null && this.host == null) {
400 try {
401 final URIAuthority uriAuthority = URIAuthority.parse(this.encodedAuthority);
402 this.encodedUserInfo = uriAuthority.getUserInfo();
403 this.userInfo = PercentCodec.decode(uriAuthority.getUserInfo(), charset);
404 this.host = PercentCodec.decode(uriAuthority.getHostName(), charset);
405 this.port = uriAuthority.getPort();
406 } catch (final URISyntaxException ignore) {
407
408 }
409 }
410 this.encodedPath = uri.getRawPath();
411 this.pathSegments = parsePath(uri.getRawPath(), charset);
412 this.pathRootless = uri.getRawPath() == null || !uri.getRawPath().startsWith("/");
413 this.encodedQuery = uri.getRawQuery();
414 this.queryParams = parseQuery(uri.getRawQuery(), charset, false);
415 this.encodedFragment = uri.getRawFragment();
416 this.fragment = uri.getFragment();
417 this.charset = charset;
418 }
419
420
421
422
423
424
425 public URIBuilder setScheme(final String scheme) {
426 this.scheme = !TextUtils.isBlank(scheme) ? scheme : null;
427 return this;
428 }
429
430
431
432
433
434
435
436
437 public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart) {
438 this.encodedSchemeSpecificPart = schemeSpecificPart;
439 return this;
440 }
441
442
443
444
445
446
447
448
449
450 public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final NameValuePair... nvps) {
451 return setSchemeSpecificPart(schemeSpecificPart, nvps != null ? Arrays.asList(nvps) : null);
452 }
453
454
455
456
457
458
459
460
461
462 public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final List <NameValuePair> nvps) {
463 this.encodedSchemeSpecificPart = null;
464 if (!TextUtils.isBlank(schemeSpecificPart)) {
465 final StringBuilder sb = new StringBuilder(schemeSpecificPart);
466 if (nvps != null && !nvps.isEmpty()) {
467 sb.append("?");
468 formatQuery(sb, nvps, this.charset, false);
469 }
470 this.encodedSchemeSpecificPart = sb.toString();
471 }
472 return this;
473 }
474
475
476
477
478
479
480
481 public URIBuilder setUserInfo(final String userInfo) {
482 this.userInfo = !TextUtils.isBlank(userInfo) ? userInfo : null;
483 this.encodedSchemeSpecificPart = null;
484 this.encodedAuthority = null;
485 this.encodedUserInfo = null;
486 return this;
487 }
488
489
490
491
492
493
494
495
496
497
498 @Deprecated
499 public URIBuilder setUserInfo(final String username, final String password) {
500 return setUserInfo(username + ':' + password);
501 }
502
503
504
505
506
507
508 public URIBuilder setHost(final InetAddress host) {
509 this.host = host != null ? host.getHostAddress() : null;
510 this.encodedSchemeSpecificPart = null;
511 this.encodedAuthority = null;
512 return this;
513 }
514
515
516
517
518
519
520
521
522 public URIBuilder setHost(final String host) {
523 this.host = host;
524 this.encodedSchemeSpecificPart = null;
525 this.encodedAuthority = null;
526 return this;
527 }
528
529
530
531
532
533
534
535 public URIBuilder setHttpHost(final HttpHost httpHost) {
536 setScheme(httpHost.getSchemeName());
537 setHost(httpHost.getHostName());
538 setPort(httpHost.getPort());
539 return this;
540 }
541
542
543
544
545
546
547 public URIBuilder setPort(final int port) {
548 this.port = port < 0 ? -1 : port;
549 this.encodedSchemeSpecificPart = null;
550 this.encodedAuthority = null;
551 return this;
552 }
553
554
555
556
557
558
559 public URIBuilder setPath(final String path) {
560 setPathSegments(path != null ? splitPath(path) : null);
561 this.pathRootless = path != null && !path.startsWith("/");
562 return this;
563 }
564
565
566
567
568
569
570 public URIBuilder appendPath(final String path) {
571 if (path != null) {
572 appendPathSegments(splitPath(path));
573 }
574 return this;
575 }
576
577
578
579
580
581
582 public URIBuilder setPathSegments(final String... pathSegments) {
583 return setPathSegments(Arrays.asList(pathSegments));
584 }
585
586
587
588
589
590
591 public URIBuilder appendPathSegments(final String... pathSegments) {
592 return appendPathSegments(Arrays.asList(pathSegments));
593 }
594
595
596
597
598
599
600
601
602
603 public URIBuilder setPathSegmentsRootless(final String... pathSegments) {
604 return setPathSegmentsRootless(Arrays.asList(pathSegments));
605 }
606
607
608
609
610
611
612 public URIBuilder setPathSegments(final List<String> pathSegments) {
613 this.pathSegments = pathSegments != null && !pathSegments.isEmpty() ? new ArrayList<>(pathSegments) : null;
614 this.encodedSchemeSpecificPart = null;
615 this.encodedPath = null;
616 this.pathRootless = false;
617 return this;
618 }
619
620
621
622
623
624
625 public URIBuilder appendPathSegments(final List<String> pathSegments) {
626 if (pathSegments != null && !pathSegments.isEmpty()) {
627 if (this.pathSegments == null) {
628 this.pathSegments = new ArrayList<>();
629 }
630 this.pathSegments.addAll(pathSegments);
631 this.encodedSchemeSpecificPart = null;
632 this.encodedPath = null;
633 }
634 return this;
635 }
636
637
638
639
640
641
642
643
644
645 public URIBuilder setPathSegmentsRootless(final List<String> pathSegments) {
646 this.pathSegments = pathSegments != null && !pathSegments.isEmpty() ? new ArrayList<>(pathSegments) : null;
647 this.encodedSchemeSpecificPart = null;
648 this.encodedPath = null;
649 this.pathRootless = true;
650 return this;
651 }
652
653
654
655
656
657
658 public URIBuilder removeQuery() {
659 this.queryParams = null;
660 this.query = null;
661 this.encodedQuery = null;
662 this.encodedSchemeSpecificPart = null;
663 return this;
664 }
665
666
667
668
669
670
671
672
673
674
675
676 public URIBuilder setParameters(final List <NameValuePair> nameValuePairs) {
677 if (this.queryParams == null) {
678 this.queryParams = new ArrayList<>();
679 } else {
680 this.queryParams.clear();
681 }
682 if (nameValuePairs != null) {
683 this.queryParams.addAll(nameValuePairs);
684 }
685 this.encodedQuery = null;
686 this.encodedSchemeSpecificPart = null;
687 this.query = null;
688 return this;
689 }
690
691
692
693
694
695
696
697
698
699
700
701 public URIBuilder addParameters(final List<NameValuePair> nameValuePairs) {
702 if (this.queryParams == null) {
703 this.queryParams = new ArrayList<>();
704 }
705 if (nameValuePairs != null) {
706 this.queryParams.addAll(nameValuePairs);
707 }
708 this.encodedQuery = null;
709 this.encodedSchemeSpecificPart = null;
710 this.query = null;
711 return this;
712 }
713
714
715
716
717
718
719
720
721
722
723
724 public URIBuilder setParameters(final NameValuePair... nameValuePairs) {
725 if (this.queryParams == null) {
726 this.queryParams = new ArrayList<>();
727 } else {
728 this.queryParams.clear();
729 }
730 if (nameValuePairs != null) {
731 Collections.addAll(this.queryParams, nameValuePairs);
732 }
733 this.encodedQuery = null;
734 this.encodedSchemeSpecificPart = null;
735 this.query = null;
736 return this;
737 }
738
739
740
741
742
743
744
745
746
747
748
749 public URIBuilder addParameter(final String param, final String value) {
750 return addParameter(new BasicNameValuePair(param, value));
751 }
752
753
754
755
756
757
758
759
760
761
762
763
764 public URIBuilder addParameter(final NameValuePair nameValuePair) {
765 if (this.queryParams == null) {
766 this.queryParams = new ArrayList<>();
767 }
768 if (nameValuePair != null) {
769 this.queryParams.add(nameValuePair);
770 }
771 this.encodedQuery = null;
772 this.encodedSchemeSpecificPart = null;
773 this.query = null;
774 return this;
775 }
776
777
778
779
780
781
782
783
784
785
786
787
788 public URIBuilder removeParameter(final String param) {
789 Args.notNull(param, "param");
790 if (this.queryParams != null && !this.queryParams.isEmpty()) {
791 this.queryParams.removeIf(nvp -> nvp.getName().equals(param));
792 }
793 this.encodedQuery = null;
794 this.encodedSchemeSpecificPart = null;
795 this.query = null;
796 return this;
797 }
798
799
800
801
802
803
804
805
806
807
808
809 public URIBuilder setParameter(final String param, final String value) {
810 if (this.queryParams == null) {
811 this.queryParams = new ArrayList<>();
812 }
813 if (!this.queryParams.isEmpty()) {
814 this.queryParams.removeIf(nvp -> nvp.getName().equals(param));
815 }
816 this.queryParams.add(new BasicNameValuePair(param, value));
817 this.encodedQuery = null;
818 this.encodedSchemeSpecificPart = null;
819 this.query = null;
820 return this;
821 }
822
823
824
825
826
827
828 public URIBuilder clearParameters() {
829 this.queryParams = null;
830 this.encodedQuery = null;
831 this.encodedSchemeSpecificPart = null;
832 return this;
833 }
834
835
836
837
838
839
840
841
842
843
844
845 public URIBuilder setCustomQuery(final String query) {
846 this.query = !TextUtils.isBlank(query) ? query : null;
847 this.encodedQuery = null;
848 this.encodedSchemeSpecificPart = null;
849 this.queryParams = null;
850 return this;
851 }
852
853
854
855
856
857
858
859 public URIBuilder setFragment(final String fragment) {
860 this.fragment = !TextUtils.isBlank(fragment) ? fragment : null;
861 this.encodedFragment = null;
862 return this;
863 }
864
865
866
867
868
869
870 public boolean isAbsolute() {
871 return this.scheme != null;
872 }
873
874
875
876
877
878
879 public boolean isOpaque() {
880 return this.pathSegments == null && this.encodedPath == null;
881 }
882
883
884
885
886
887
888 public String getScheme() {
889 return this.scheme;
890 }
891
892
893
894
895
896
897
898 public String getSchemeSpecificPart() {
899 return this.encodedSchemeSpecificPart;
900 }
901
902
903
904
905
906
907 public String getUserInfo() {
908 return this.userInfo;
909 }
910
911
912
913
914
915
916
917 public String getHost() {
918 return this.host;
919 }
920
921
922
923
924
925
926 public int getPort() {
927 return this.port;
928 }
929
930
931
932
933
934
935 public boolean isPathEmpty() {
936 return (this.pathSegments == null || this.pathSegments.isEmpty()) &&
937 (this.encodedPath == null || this.encodedPath.isEmpty());
938 }
939
940
941
942
943
944
945 public List<String> getPathSegments() {
946 return this.pathSegments != null ? new ArrayList<>(this.pathSegments) : new ArrayList<>();
947 }
948
949
950
951
952
953
954 public String getPath() {
955 if (this.pathSegments == null) {
956 return null;
957 }
958 final StringBuilder result = new StringBuilder();
959 for (final String segment : this.pathSegments) {
960 result.append('/').append(segment);
961 }
962 return result.toString();
963 }
964
965
966
967
968
969
970 public boolean isQueryEmpty() {
971 return (this.queryParams == null || this.queryParams.isEmpty()) && this.encodedQuery == null;
972 }
973
974
975
976
977
978
979 public List<NameValuePair> getQueryParams() {
980 return this.queryParams != null ? new ArrayList<>(this.queryParams) : new ArrayList<>();
981 }
982
983
984
985
986
987
988
989
990 public NameValuePair getFirstQueryParam(final String name) {
991 if (queryParams == null) {
992 return null;
993 }
994 return queryParams.stream().filter(e -> name.equals(e.getName())).findFirst().orElse(null);
995 }
996
997
998
999
1000
1001
1002 public String getFragment() {
1003 return this.fragment;
1004 }
1005
1006
1007
1008
1009
1010
1011 @Deprecated
1012 public URIBuilder normalizeSyntax() {
1013 return optimize();
1014 }
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029 public URIBuilder optimize() {
1030 final String scheme = this.scheme;
1031 if (scheme != null) {
1032 this.scheme = TextUtils.toLowerCase(scheme);
1033 }
1034
1035 if (this.pathRootless) {
1036 return this;
1037 }
1038
1039
1040 this.encodedSchemeSpecificPart = null;
1041 this.encodedAuthority = null;
1042 this.encodedUserInfo = null;
1043 this.encodedPath = null;
1044 this.encodedQuery = null;
1045 this.encodedFragment = null;
1046
1047 final String host = this.host;
1048 if (host != null) {
1049 this.host = TextUtils.toLowerCase(host);
1050 }
1051
1052 if (this.pathSegments != null) {
1053 final List<String> inputSegments = this.pathSegments;
1054 if (!inputSegments.isEmpty()) {
1055 final LinkedList<String> outputSegments = new LinkedList<>();
1056 for (final String inputSegment : inputSegments) {
1057 if (!inputSegment.isEmpty() && !".".equals(inputSegment)) {
1058 if ("..".equals(inputSegment)) {
1059 if (!outputSegments.isEmpty()) {
1060 outputSegments.removeLast();
1061 }
1062 } else {
1063 outputSegments.addLast(inputSegment);
1064 }
1065 }
1066 }
1067 if (!inputSegments.isEmpty()) {
1068 final String lastSegment = inputSegments.get(inputSegments.size() - 1);
1069 if (lastSegment.isEmpty()) {
1070 outputSegments.addLast("");
1071 }
1072 }
1073 this.pathSegments = outputSegments;
1074 } else {
1075 this.pathSegments = Collections.singletonList("");
1076 }
1077 }
1078
1079 return this;
1080 }
1081
1082
1083
1084
1085
1086
1087 @Override
1088 public String toString() {
1089 return buildString();
1090 }
1091
1092 }