001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.http.classic.part; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.PredicateUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.commons.utils.StringUtils.*; 023 024import java.util.*; 025import java.util.function.*; 026import java.util.stream.*; 027 028import org.apache.http.*; 029import org.apache.http.util.*; 030import org.apache.juneau.commons.svl.*; 031import org.apache.juneau.commons.utils.*; 032import org.apache.juneau.http.*; 033 034/** 035 * An simple list of HTTP parts (form-data, query-parameters, path-parameters). 036 * 037 * <h5 class='figure'>Example</h5> 038 * <p class='bjava'> 039 * PartList <jv>parts</jv> = PartList 040 * .<jsm>create</jsm>() 041 * .append(MyPart.<jsm>of</jsm>(<js>"foo"</js>)) 042 * .append(<js>"Bar"</js>, ()-><jsm>getDynamicValueFromSomewhere</jsm>()); 043 * </p> 044 * 045 * <h5 class='section'>See Also:</h5><ul> 046 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a> 047 * </ul> 048 */ 049@SuppressWarnings({ 050 "java:S110", // Inheritance depth acceptable for this class hierarchy 051 "java:S127", // the set(...) replace loops shrink the cached loop bound (j--) when a same-named part is removed during iteration 052 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 053 "java:S2160" // equals() inherited from ArrayList; list equality is element-based, which is correct for part lists 054}) 055public class PartList extends ArrayList<NameValuePair> { 056 057 /** Represents no part list in annotations. */ 058 public static final class Void extends PartList { 059 private static final long serialVersionUID = 1L; 060 } 061 062 private static final long serialVersionUID = 1L; 063 064 /** 065 * Instantiates a new part list. 066 * 067 * @return A new part list. 068 */ 069 public static PartList create() { 070 return new PartList(); 071 } 072 073 /** 074 * Creates a new {@link PartList} initialized with the specified parts. 075 * 076 * @param parts 077 * The parts to add to the list. 078 * <br>Can be <jk>null</jk>. 079 * <br><jk>null</jk> entries are ignored. 080 * @return A new instance, never <jk>null</jk>. 081 */ 082 public static PartList of(List<NameValuePair> parts) { 083 return new PartList().append(parts); 084 } 085 086 /** 087 * Creates a new {@link PartList} initialized with the specified parts. 088 * 089 * @param parts 090 * The parts to add to the list. 091 * <br><jk>null</jk> entries are ignored. 092 * @return A new instance, never <jk>null</jk>. 093 */ 094 public static PartList of(NameValuePair...parts) { 095 return new PartList().append(parts); 096 } 097 098 /** 099 * Creates a new {@link PartList} initialized with the specified name/value pairs. 100 * 101 * <h5 class='figure'>Example</h5> 102 * <p class='bjava'> 103 * PartList <jv>parts</jv> = PartList.<jsm>ofPairs</jsm>(<js>"foo"</js>, 1, <js>"bar"</js>, <jk>true</jk>); 104 * </p> 105 * 106 * @param pairs 107 * Initial list of pairs. 108 * <br>Must be an even number of parameters representing key/value pairs. 109 * @throws RuntimeException If odd number of parameters were specified. 110 * @return A new instance. 111 */ 112 public static PartList ofPairs(String...pairs) { 113 var x = new PartList(); 114 if (pairs == null) 115 pairs = new String[0]; 116 req(pairs.length % 2 == 0, "Odd number of parameters passed into PartList.ofPairs()"); 117 for (var i = 0; i < pairs.length; i += 2) 118 x.add(BasicPart.of(pairs[i], pairs[i + 1])); 119 return x; 120 } 121 122 private transient VarResolver varResolver; 123 boolean caseInsensitive; 124 125 /** 126 * Constructor. 127 */ 128 public PartList() { 129 super(); 130 } 131 132 /** 133 * Copy constructor. 134 * 135 * @param copyFrom The bean to copy. Must not be <jk>null</jk>. 136 */ 137 protected PartList(PartList copyFrom) { 138 super(copyFrom); 139 caseInsensitive = copyFrom.caseInsensitive; 140 } 141 142 /** 143 * Adds the specified parts to the end of the parts in this list. 144 * 145 * @param values The parts to add. <jk>null</jk> values are ignored. 146 * @return This object. 147 */ 148 public PartList append(List<NameValuePair> values) { 149 if (nn(values)) 150 values.forEach(this::append); 151 return this; 152 } 153 154 /** 155 * Adds the specified part to the end of the parts in this list. 156 * 157 * @param value The part to add. <jk>null</jk> values are ignored. 158 * @return This object. 159 */ 160 public PartList append(NameValuePair value) { 161 if (nn(value)) 162 add(value); 163 return this; 164 } 165 166 /** 167 * Adds the specified parts to the end of the parts in this list. 168 * 169 * @param values The parts to add. <jk>null</jk> values are ignored. 170 * @return This object. 171 */ 172 public PartList append(NameValuePair...values) { 173 if (nn(values)) 174 for (var value : values) 175 append(value); 176 return this; 177 } 178 179 /** 180 * Appends the specified part to the end of this list. 181 * 182 * <p> 183 * The part is added as a {@link BasicPart}. 184 * 185 * @param name The part name. 186 * @param value The part value. 187 * @return This object. 188 */ 189 public PartList append(String name, Object value) { 190 return append(createPart(name, value)); 191 } 192 193 /** 194 * Appends the specified part to the end of this list using a value supplier. 195 * 196 * <p> 197 * The part is added as a {@link BasicPart}. 198 * 199 * <p> 200 * Value is re-evaluated on each call to {@link BasicPart#getValue()}. 201 * 202 * @param name The part name. 203 * @param value The part value supplier. 204 * @return This object. 205 */ 206 public PartList append(String name, Supplier<?> value) { 207 return append(createPart(name, value)); 208 } 209 210 /** 211 * Specifies that the parts in this list should be treated as case-sensitive. 212 * 213 * <p> 214 * The default behavior is case-sensitive. 215 * 216 * @param value The new value for this setting. 217 * @return This object. 218 */ 219 public PartList caseInsensitive(boolean value) { 220 caseInsensitive = value; 221 return this; 222 } 223 224 /** 225 * Tests if parts with the given name are contained within this list. 226 * 227 * <p> 228 * Part name comparison is case insensitive. 229 * 230 * @param name The part name. 231 * @return <jk>true</jk> if at least one part with the name is present. 232 */ 233 public boolean contains(String name) { 234 return stream().anyMatch(x -> eq(x.getName(), name)); 235 } 236 237 /** 238 * Makes a copy of this list. 239 * 240 * @return A new copy of this list. 241 */ 242 public PartList copy() { 243 return new PartList(this); 244 } 245 246 /** 247 * Performs an action on all the matching parts in this list. 248 * 249 * <p> 250 * This is the preferred method for iterating over parts as it does not involve 251 * creation or copy of lists/arrays. 252 * 253 * @param filter A predicate to apply to each element to determine if it should be included. Can be <jk>null</jk> (if <jk>null</jk>, all elements are accepted). 254 * @param action An action to perform on each element. 255 * @return This object. 256 */ 257 public PartList forEach(Predicate<NameValuePair> filter, Consumer<NameValuePair> action) { 258 forEach(x -> consumeIf(filter, action, x)); 259 return this; 260 } 261 262 /** 263 * Performs an action on all parts in this list with the specified name. 264 * 265 * <p> 266 * This is the preferred method for iterating over parts as it does not involve 267 * creation or copy of lists/arrays. 268 * 269 * @param name The parts name. 270 * @param action An action to perform on each element. 271 * @return This object. 272 */ 273 public PartList forEach(String name, Consumer<NameValuePair> action) { 274 return forEach(x -> eq(name, x.getName()), action); 275 } 276 277 /** 278 * Performs an action on the values for all matching parts in this list. 279 * 280 * @param filter A predicate to apply to each element to determine if it should be included. Can be <jk>null</jk> (if <jk>null</jk>, all elements are accepted). 281 * @param action An action to perform on each element. 282 * @return This object. 283 */ 284 public PartList forEachValue(Predicate<NameValuePair> filter, Consumer<String> action) { 285 return forEach(filter, x -> action.accept(x.getValue())); 286 } 287 288 /** 289 * Performs an action on the values of all matching parts in this list. 290 * 291 * @param name The part name. 292 * @param action An action to perform on each element. 293 * @return This object. 294 */ 295 public PartList forEachValue(String name, Consumer<String> action) { 296 return forEach(name, x -> action.accept(x.getValue())); 297 } 298 299 /** 300 * Gets a part representing all of the part values with the given name. 301 * 302 * <p> 303 * Same as {@link #get(String, Class)} but the part name is pulled from the name or value attribute of the {@link FormData}/{@link Query}/{@link Path} annotation. 304 * 305 * <h5 class='figure'>Example</h5> 306 * <p class='bjava'> 307 * Age <jv>age</jv> = <jv>partList</jv>.get(Age.<jk>class</jk>); 308 * </p> 309 * 310 * @param <T> The part implementation class. 311 * @param type The part implementation class. Must not be <jk>null</jk>. 312 * @return A part with a condensed value, or an empty {@link Optional} if no parts by the given name are present. 313 */ 314 public <T> Optional<T> get(Class<T> type) { 315 reqnn("type", type); 316 317 var name = PartBeanMeta.of(type).getSchema().getName(); 318 req(nn(name), "Part name could not be found on bean type '%s'", cn(type)); 319 320 return get(name, type); 321 } 322 323 /** 324 * Gets a part representing all of the part values with the given name. 325 * 326 * <p> 327 * If more that one part with the given name exists the values will be combined with <js>", "</js> as per 328 * <a href='https://tools.ietf.org/html/rfc2616#section-4.2'>RFC 2616 Section 4.2</a>. 329 * 330 * @param name The part name. 331 * @return A part with a condensed value, or {@link Optional#empty()} if no parts by the given name are present 332 */ 333 public Optional<NameValuePair> get(String name) { 334 335 NameValuePair first = null; 336 List<NameValuePair> rest = null; 337 for (var x : this) { 338 if (eq(x.getName(), name)) { 339 if (first == null) 340 first = x; 341 else { 342 if (rest == null) 343 rest = list(); 344 rest.add(x); 345 } 346 } 347 } 348 349 if (first == null) 350 return oe(); 351 352 if (rest == null) 353 return o(first); 354 355 var sb = new CharArrayBuffer(128); 356 sb.append(first.getValue()); 357 for (var element : rest) { 358 sb.append(','); 359 sb.append(element.getValue()); 360 } 361 362 return o(new BasicStringPart(name, sb.toString())); 363 } 364 365 /** 366 * Gets a part representing all of the part values with the given name. 367 * 368 * <p> 369 * If more that one part with the given name exists the values will be combined with <js>", "</js> as per 370 * <a href='https://tools.ietf.org/html/rfc2616#section-4.2'>RFC 2616 Section 4.2</a>. 371 * 372 * <p> 373 * The implementation class must have a public constructor taking in one of the following argument lists: 374 * <ul> 375 * <li><c>X(String <jv>value</jv>)</c> 376 * <li><c>X(Object <jv>value</jv>)</c> 377 * <li><c>X(String <jv>name</jv>, String <jv>value</jv>)</c> 378 * <li><c>X(String <jv>name</jv>, Object <jv>value</jv>)</c> 379 * </ul> 380 * 381 * <h5 class='figure'>Example</h5> 382 * <p class='bjava'> 383 * BasicIntegerPart <jv>age</jv> = <jv>partList</jv>.get(<js>"age"</js>, BasicIntegerPart.<jk>class</jk>); 384 * </p> 385 * 386 * @param <T> The part implementation class. 387 * @param name The part name. 388 * @param type The part implementation class. 389 * @return A part with a condensed value, or an empty {@link Optional} if no parts by the given name are present. 390 */ 391 public <T> Optional<T> get(String name, Class<T> type) { 392 393 NameValuePair first = null; 394 List<NameValuePair> rest = null; 395 for (var x : this) { 396 if (eq(x.getName(), name)) { 397 if (first == null) 398 first = x; 399 else { 400 if (rest == null) 401 rest = list(); 402 rest.add(x); 403 } 404 } 405 } 406 407 if (first == null) 408 return oe(); 409 410 if (rest == null) 411 return o(PartBeanMeta.of(type).construct(name, first.getValue())); 412 413 var sb = new CharArrayBuffer(128); 414 sb.append(first.getValue()); 415 for (var element : rest) { 416 sb.append(','); 417 sb.append(element.getValue()); 418 } 419 420 return o(PartBeanMeta.of(type).construct(name, sb.toString())); 421 } 422 423 /** 424 * Gets all of the parts. 425 * 426 * <p> 427 * The returned array maintains the relative order in which the parts were added. 428 * Each call creates a new array not backed by this list. 429 * 430 * <p> 431 * As a general rule, it's more efficient to use the other methods with consumers to 432 * get parts. 433 * 434 * @return An array containing all parts, never <jk>null</jk>. 435 */ 436 public NameValuePair[] getAll() { return stream().toArray(NameValuePair[]::new); } 437 438 /** 439 * Gets all of the parts with the given name. 440 * 441 * <p> 442 * The returned array maintains the relative order in which the parts were added. 443 * Part name comparison is case sensitive by default. 444 * Parts with null values are ignored. 445 * Each call creates a new array not backed by this list. 446 * 447 * <p> 448 * As a general rule, it's more efficient to use the other methods with consumers to 449 * get parts. 450 * 451 * @param name The part name. 452 * 453 * @return An array containing all matching parts, never <jk>null</jk>. 454 */ 455 public NameValuePair[] getAll(String name) { 456 return stream().filter(x -> eq(x.getName(), name)).toArray(NameValuePair[]::new); 457 } 458 459 /** 460 * Gets the first part with the given name. 461 * 462 * <p> 463 * Part name comparison is case sensitive by default. 464 * 465 * @param name The part name. 466 * @return The first matching part, or {@link Optional#empty()} if not found. 467 */ 468 public Optional<NameValuePair> getFirst(String name) { 469 for (var i = 0; i < size(); i++) { 470 var x = get(i); 471 if (eq(x.getName(), name)) 472 return o(x); 473 } 474 return oe(); 475 } 476 477 /** 478 * Gets the last part with the given name. 479 * 480 * <p> 481 * Part name comparison is case sensitive by default. 482 * 483 * @param name The part name. 484 * @return The last matching part, or {@link Optional#empty()} if not found. 485 */ 486 public Optional<NameValuePair> getLast(String name) { 487 for (var i = size() - 1; i >= 0; i--) { 488 var x = get(i); 489 if (eq(x.getName(), name)) 490 return o(x); 491 } 492 return oe(); 493 } 494 495 /** 496 * Returns all the string values for all parts with the specified name. 497 * 498 * @param name The part name. 499 * @return An array containing all values. Never <jk>null</jk>. 500 */ 501 public String[] getValues(String name) { 502 return stream().filter(x -> eq(x.getName(), name)).map(NameValuePair::getValue).toArray(String[]::new); 503 } 504 505 /** 506 * Returns an iterator over this list of parts. 507 * 508 * @return A new iterator over this list of parts. 509 */ 510 public PartIterator partIterator() { 511 return new BasicPartIterator(toArray(new NameValuePair[0]), null, caseInsensitive); 512 } 513 514 /** 515 * Returns an iterator over the parts with a given name in this list. 516 * 517 * @param name The name of the parts over which to iterate, or <jk>null</jk> for all parts 518 * 519 * @return A new iterator over the matching parts in this list. 520 */ 521 public PartIterator partIterator(String name) { 522 return new BasicPartIterator(getAll(name), name, caseInsensitive); 523 } 524 525 /** 526 * Adds the specified parts to the beginning of the parts in this list. 527 * 528 * @param values The parts to add. <jk>null</jk> values are ignored. 529 * @return This object. 530 */ 531 public PartList prepend(List<NameValuePair> values) { 532 if (nn(values)) 533 addAll(0, values); 534 return this; 535 } 536 537 /** 538 * Adds the specified part to the beginning of the parts in this list. 539 * 540 * @param value The part to add. <jk>null</jk> values are ignored. 541 * @return This object. 542 */ 543 public PartList prepend(NameValuePair value) { 544 if (nn(value)) 545 add(0, value); 546 return this; 547 } 548 549 /** 550 * Adds the specified parts to the beginning of the parts in this list. 551 * 552 * @param values The parts to add. <jk>null</jk> values are ignored. 553 * @return This object. 554 */ 555 public PartList prepend(NameValuePair...values) { 556 if (nn(values)) 557 prepend(l(values)); 558 return this; 559 } 560 561 /** 562 * Appends the specified part to the beginning of this list. 563 * 564 * <p> 565 * The part is added as a {@link BasicPart}. 566 * 567 * @param name The part name. 568 * @param value The part value. 569 * @return This object. 570 */ 571 public PartList prepend(String name, Object value) { 572 return prepend(createPart(name, value)); 573 } 574 575 /** 576 * Appends the specified part to the beginning of this list using a value supplier. 577 * 578 * <p> 579 * The part is added as a {@link BasicPart}. 580 * 581 * <p> 582 * Value is re-evaluated on each call to {@link BasicPart#getValue()}. 583 * 584 * @param name The part name. 585 * @param value The part value supplier. 586 * @return This object. 587 */ 588 public PartList prepend(String name, Supplier<?> value) { 589 return prepend(createPart(name, value)); 590 } 591 592 /** 593 * Removes the specified parts from this list. 594 * 595 * @param values The parts to remove. <jk>null</jk> values are ignored. 596 * @return This object. 597 */ 598 public PartList remove(List<NameValuePair> values) { 599 if (nn(values)) 600 values.forEach(this::remove); 601 return this; 602 } 603 604 /** 605 * Removes the specified part from this list. 606 * 607 * @param value The part to remove. <jk>null</jk> values are ignored. 608 * @return This object. 609 */ 610 public PartList remove(NameValuePair value) { 611 if (nn(value)) 612 removeIf(x -> eq(x.getName(), value.getName()) && eq(x.getValue(), value.getValue())); 613 return this; 614 } 615 616 /** 617 * Removes the specified parts from this list. 618 * 619 * @param values The parts to remove. <jk>null</jk> values are ignored. 620 * @return This object. 621 */ 622 public PartList remove(NameValuePair...values) { 623 for (var value : values) 624 remove(value); 625 return this; 626 } 627 628 /** 629 * Removes the part with the specified name from this list. 630 * 631 * @param name The part name. 632 * @return This object. 633 */ 634 public PartList remove(String name) { 635 removeIf(x -> eq(x.getName(), name)); 636 return this; 637 } 638 639 /** 640 * Removes the part with the specified name from this list. 641 * 642 * @param names The part name. 643 * @return This object. 644 */ 645 public PartList remove(String...names) { 646 if (nn(names)) 647 for (var name : names) 648 remove(name); 649 return this; 650 } 651 652 /** 653 * Allows part values to contain SVL variables. 654 * 655 * <p> 656 * Resolves variables in part values when using the following methods: 657 * <ul> 658 * <li class='jm'>{@link #append(String, Object) append(String,Object)} 659 * <li class='jm'>{@link #append(String, Supplier) append(String,Supplier<?>)} 660 * <li class='jm'>{@link #prepend(String, Object) prepend(String,Object)} 661 * <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier<?>)} 662 * <li class='jm'>{@link #set(String, Object) set(String,Object)} 663 * <li class='jm'>{@link #set(String, Supplier) set(String,Supplier<?>)} 664 * </ul> 665 * 666 * <p> 667 * Uses {@link VarResolver#DEFAULT} to resolve variables. 668 * 669 * @return This object. 670 */ 671 public PartList resolving() { 672 return resolving(VarResolver.DEFAULT); 673 } 674 675 /** 676 * Allows part values to contain SVL variables. 677 * 678 * <p> 679 * Resolves variables in part values when using the following methods: 680 * <ul> 681 * <li class='jm'>{@link #append(String, Object) append(String,Object)} 682 * <li class='jm'>{@link #append(String, Supplier) append(String,Supplier<?>)} 683 * <li class='jm'>{@link #prepend(String, Object) prepend(String,Object)} 684 * <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier<?>)} 685 * <li class='jm'>{@link #set(String, Object) set(String,Object)} 686 * <li class='jm'>{@link #set(String, Supplier) set(String,Supplier<?>)} 687 * </ul> 688 * 689 * @param varResolver The variable resolver to use for resolving variables. Can be <jk>null</jk> (disables variable resolution). 690 * @return This object. 691 */ 692 public PartList resolving(VarResolver varResolver) { 693 this.varResolver = varResolver; 694 return this; 695 } 696 697 /** 698 * Replaces the first occurrence of the parts with the same name. 699 * 700 * <p> 701 * If no part with the same name is found the given part is added to the end of the list. 702 * 703 * @param values The parts to replace. <jk>null</jk> values are ignored. 704 * @return This object. 705 */ 706 @SuppressWarnings({ 707 "java:S3776" // Cognitive complexity acceptable for this specific logic 708 }) 709 public PartList set(List<NameValuePair> values) { 710 711 if (nn(values)) { 712 for (var h : values) { 713 if (nn(h)) { 714 for (int i2 = 0, j2 = size(); i2 < j2; i2++) { 715 var x = get(i2); 716 if (eq(x.getName(), h.getName())) { 717 remove(i2); 718 j2--; 719 } 720 } 721 } 722 } 723 724 for (var x : values) { 725 if (nn(x)) { 726 add(x); 727 } 728 } 729 } 730 731 return this; 732 } 733 734 /** 735 * Adds or replaces the part(s) with the same name. 736 * 737 * <p> 738 * If no part with the same name is found the given part is added to the end of the list. 739 * 740 * @param value The part to replace. <jk>null</jk> values are ignored. 741 * @return This object. 742 */ 743 public PartList set(NameValuePair value) { 744 if (nn(value)) { 745 var replaced = false; 746 for (int i = 0, j = size(); i < j; i++) { 747 var x = get(i); 748 if (eq(x.getName(), value.getName())) { 749 if (replaced) { 750 remove(i); 751 j--; 752 } else { 753 set(i, value); 754 replaced = true; 755 } 756 } 757 } 758 759 if (! replaced) 760 add(value); 761 } 762 763 return this; 764 } 765 766 /** 767 * Adds or replaces the part(s) with the same name. 768 * 769 * <p> 770 * If no part with the same name is found the given part is added to the end of the list. 771 * 772 * @param values The part to replace. <jk>null</jk> values are ignored. 773 * @return This object. 774 */ 775 public PartList set(NameValuePair...values) { 776 if (nn(values)) 777 set(l(values)); 778 return this; 779 } 780 781 /** 782 * Replaces the first occurrence of the parts with the same name. 783 * 784 * @param name The part name. 785 * @param value The part value. 786 * @return This object. 787 */ 788 public PartList set(String name, Object value) { 789 return set(createPart(name, value)); 790 } 791 792 /** 793 * Replaces the first occurrence of the parts with the same name. 794 * 795 * @param name The part name. 796 * @param value The part value. 797 * @return This object. 798 */ 799 public PartList set(String name, Supplier<?> value) { 800 return set(createPart(name, value)); 801 } 802 803 /** 804 * Adds a collection of default parts. 805 * 806 * <p> 807 * Default parts are set if they're not already in the list. 808 * 809 * @param parts The list of default parts. 810 * @return This object. 811 */ 812 public PartList setDefault(List<NameValuePair> parts) { 813 if (nn(parts)) 814 parts.stream().filter(x -> nn(x) && ! contains(x.getName())).forEach(this::set); 815 return this; 816 } 817 818 /** 819 * Makes a copy of this list of parts and adds a collection of default parts. 820 * 821 * <p> 822 * Default parts are set if they're not already in the list. 823 * 824 * @param parts The list of default parts. 825 * @return A new list, or the same list if the parts were empty. 826 */ 827 public PartList setDefault(NameValuePair...parts) { 828 if (nn(parts)) 829 setDefault(l(parts)); 830 return this; 831 } 832 833 /** 834 * Replaces the first occurrence of the part with the same name. 835 * 836 * @param name The header name. 837 * @param value The header value. 838 * @return This object. 839 */ 840 public PartList setDefault(String name, Object value) { 841 return setDefault(createPart(name, value)); 842 } 843 844 /** 845 * Replaces the first occurrence of the headers with the same name. 846 * 847 * @param name The header name. 848 * @param value The header value. 849 * @return This object. 850 */ 851 public PartList setDefault(String name, Supplier<?> value) { 852 return setDefault(createPart(name, value)); 853 } 854 855 /** 856 * Returns an unmodifiable snapshot of this list. 857 * 858 * <p> 859 * Idempotency: if this list is already an {@link Unmodifiable} snapshot, it is returned as-is. 860 * 861 * @return An unmodifiable snapshot of this list, or this list if it is already unmodifiable. 862 */ 863 public PartList unmodifiable() { 864 return this instanceof Unmodifiable ? this : new Unmodifiable(this); 865 } 866 867 /** 868 * Returns <jk>true</jk> if this list is an unmodifiable snapshot. 869 * 870 * @return <jk>true</jk> if this list is an {@link Unmodifiable} snapshot. 871 */ 872 public boolean isUnmodifiable() { 873 return this instanceof UnmodifiableBean; 874 } 875 876 /** 877 * Returns a stream of the parts in this list with the specified name. 878 * 879 * <p> 880 * This does not involve a copy of the underlying array of <c>NameValuePair</c> objects so should perform well. 881 * 882 * @param name The part name. 883 * @return This object. 884 */ 885 public Stream<NameValuePair> stream(String name) { 886 return stream().filter(x -> eq(name, x.getName())); 887 } 888 889 /** 890 * Returns this list as a URL-encoded custom query. 891 */ 892 @Override /* Overridden from Object */ 893 public String toString() { 894 var sb = new StringBuilder(); 895 forEach(p -> { 896 if (nn(p)) { 897 var v = p.getValue(); 898 if (nn(v)) { 899 if (!sb.isEmpty()) 900 sb.append("&"); 901 sb.append(urlEncode(p.getName())).append('=').append(urlEncode(p.getValue())); 902 } 903 } 904 }); 905 return sb.toString(); 906 } 907 908 private NameValuePair createPart(String name, Object value) { 909 var isResolving = nn(varResolver); 910 911 if (value instanceof Supplier<?> value2) { 912 return new BasicPart(name, isResolving ? resolver(value2) : value2); 913 } 914 return new BasicPart(name, isResolving ? resolver(value) : value); 915 } 916 917 private boolean eq(String s1, String s2) { 918 return Shorts.eq(caseInsensitive, s1, s2); // NOAI 919 } 920 921 private Supplier<Object> resolver(Object input) { 922 return () -> varResolver.resolve(s(unwrap(input))); 923 } 924 925 private static Object unwrap(Object o) { 926 while (o instanceof Supplier<?> s) 927 o = s.get(); 928 return o; 929 } 930 931 /** 932 * An unmodifiable snapshot of a {@link PartList}. 933 * 934 * <p> 935 * This is the "collection-mutator-override" variant of the immutability paradigm. Because {@link PartList} 936 * extends {@link ArrayList}, a single {@code modify()} funnel cannot intercept the inherited {@code ArrayList} 937 * mutators, so this subclass snapshot-copies the elements in its constructor and overrides the entire 938 * collection-mutator surface — its own fluent mutators plus the inherited {@code ArrayList} mutators (including the 939 * {@code iterator()} / {@code listIterator()} mutators) — to throw {@link UnsupportedOperationException}. Read 940 * operations and non-mutating iteration continue to work. 941 */ 942 public static class Unmodifiable extends PartList implements UnmodifiableBean { 943 944 private static final long serialVersionUID = 1L; 945 946 /** 947 * Constructor. 948 * 949 * @param copyFrom The list to snapshot-copy. Must not be <jk>null</jk>. 950 */ 951 Unmodifiable(PartList copyFrom) { 952 super(copyFrom); 953 } 954 955 // --- Fluent mutators ----------------------------------------------------------------------------------------- 956 957 @Override public PartList append(List<NameValuePair> values) { throw uoex(RO); } 958 @Override public PartList append(NameValuePair value) { throw uoex(RO); } 959 @Override public PartList append(NameValuePair...values) { throw uoex(RO); } 960 @Override public PartList append(String name, Object value) { throw uoex(RO); } 961 @Override public PartList append(String name, Supplier<?> value) { throw uoex(RO); } 962 @Override public PartList caseInsensitive(boolean value) { throw uoex(RO); } 963 @Override public PartList prepend(List<NameValuePair> values) { throw uoex(RO); } 964 @Override public PartList prepend(NameValuePair value) { throw uoex(RO); } 965 @Override public PartList prepend(NameValuePair...values) { throw uoex(RO); } 966 @Override public PartList prepend(String name, Object value) { throw uoex(RO); } 967 @Override public PartList prepend(String name, Supplier<?> value) { throw uoex(RO); } 968 @Override public PartList remove(List<NameValuePair> values) { throw uoex(RO); } 969 @Override public PartList remove(NameValuePair value) { throw uoex(RO); } 970 @Override public PartList remove(NameValuePair...values) { throw uoex(RO); } 971 @Override public PartList remove(String name) { throw uoex(RO); } 972 @Override public PartList remove(String...names) { throw uoex(RO); } 973 @Override public PartList resolving() { throw uoex(RO); } 974 @Override public PartList resolving(VarResolver varResolver) { throw uoex(RO); } 975 @Override public PartList set(List<NameValuePair> values) { throw uoex(RO); } 976 @Override public PartList set(NameValuePair value) { throw uoex(RO); } 977 @Override public PartList set(NameValuePair...values) { throw uoex(RO); } 978 @Override public PartList set(String name, Object value) { throw uoex(RO); } 979 @Override public PartList set(String name, Supplier<?> value) { throw uoex(RO); } 980 @Override public PartList setDefault(List<NameValuePair> parts) { throw uoex(RO); } 981 @Override public PartList setDefault(NameValuePair...parts) { throw uoex(RO); } 982 @Override public PartList setDefault(String name, Object value) { throw uoex(RO); } 983 @Override public PartList setDefault(String name, Supplier<?> value) { throw uoex(RO); } 984 985 // --- Inherited ArrayList collection mutators ----------------------------------------------------------------- 986 987 @Override public boolean add(NameValuePair e) { throw uoex(RO); } 988 @Override public void add(int index, NameValuePair element) { throw uoex(RO); } 989 @Override public boolean addAll(Collection<? extends NameValuePair> c) { throw uoex(RO); } 990 @Override public boolean addAll(int index, Collection<? extends NameValuePair> c) { throw uoex(RO); } 991 @Override public void clear() { throw uoex(RO); } 992 @Override public boolean remove(Object o) { throw uoex(RO); } 993 @Override public NameValuePair remove(int index) { throw uoex(RO); } 994 @Override public boolean removeAll(Collection<?> c) { throw uoex(RO); } 995 @Override public boolean removeIf(Predicate<? super NameValuePair> filter) { throw uoex(RO); } 996 @Override public void replaceAll(UnaryOperator<NameValuePair> operator) { throw uoex(RO); } 997 @Override public boolean retainAll(Collection<?> c) { throw uoex(RO); } 998 @Override public NameValuePair set(int index, NameValuePair element) { throw uoex(RO); } 999 @Override public void sort(Comparator<? super NameValuePair> c) { throw uoex(RO); } 1000 1001 @Override public Iterator<NameValuePair> iterator() { 1002 var i = super.iterator(); 1003 return new Iterator<>() { 1004 @Override public boolean hasNext() { return i.hasNext(); } 1005 @Override public NameValuePair next() { return i.next(); } 1006 @Override public void remove() { throw uoex(RO); } 1007 }; 1008 } 1009 1010 @Override public ListIterator<NameValuePair> listIterator() { return listIterator(0); } 1011 1012 @Override public ListIterator<NameValuePair> listIterator(int index) { 1013 var i = super.listIterator(index); 1014 return new ListIterator<>() { 1015 @Override public boolean hasNext() { return i.hasNext(); } 1016 @Override public NameValuePair next() { return i.next(); } 1017 @Override public boolean hasPrevious() { return i.hasPrevious(); } 1018 @Override public NameValuePair previous() { return i.previous(); } 1019 @Override public int nextIndex() { return i.nextIndex(); } 1020 @Override public int previousIndex() { return i.previousIndex(); } 1021 @Override public void add(NameValuePair e) { throw uoex(RO); } 1022 @Override public void set(NameValuePair e) { throw uoex(RO); } 1023 @Override public void remove() { throw uoex(RO); } 1024 }; 1025 } 1026 } 1027 1028 private static final String RO = "Bean is unmodifiable."; 1029}