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