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