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>, ()-&gt;<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&lt;?&gt;)}
689    *    <li class='jm'>{@link #prepend(String, Object) prepend(String,Object)}
690    *    <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier&lt;?&gt;)}
691    *    <li class='jm'>{@link #set(String, Object) set(String,Object)}
692    *    <li class='jm'>{@link #set(String, Supplier) set(String,Supplier&lt;?&gt;)}
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&lt;?&gt;)}
712    *    <li class='jm'>{@link #prepend(String, Object) prepend(String,Object)}
713    *    <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier&lt;?&gt;)}
714    *    <li class='jm'>{@link #set(String, Object) set(String,Object)}
715    *    <li class='jm'>{@link #set(String, Supplier) set(String,Supplier&lt;?&gt;)}
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}