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.header;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.PredicateUtils.*;
022import static org.apache.juneau.commons.utils.StringUtils.*;
023import static org.apache.juneau.commons.utils.Utils.*;
024
025import java.util.*;
026import java.util.function.*;
027import java.util.stream.*;
028
029import org.apache.http.*;
030import org.apache.http.util.*;
031import org.apache.juneau.commons.collections.*;
032import org.apache.juneau.commons.utils.*;
033import org.apache.juneau.http.HttpHeaders;
034import org.apache.juneau.svl.*;
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/JuneauRestCommonBasics">juneau-rest-common Basics</a>
063 * </ul>
064 */
065public class HeaderList extends ControlledArrayList<Header> {
066
067   /** Represents no header list in annotations. */
068   public static final class Void extends HeaderList {
069      private static final long serialVersionUID = 1L;
070   }
071
072   private static final long serialVersionUID = 1L;
073
074   /**
075    * Instantiates a new list.
076    *
077    * @return A new list.
078    */
079   public static HeaderList create() {
080      return new HeaderList();
081   }
082
083   /**
084    * Creates a new {@link HeaderList} initialized with the specified headers.
085    *
086    * @param headers
087    *    The headers to add to the list.
088    *    <br><jk>null</jk> entries are ignored.
089    * @return A new unmodifiable instance, never <jk>null</jk>.
090    */
091   public static HeaderList of(Header...headers) {
092      return new HeaderList().append(headers);
093   }
094
095   /**
096    * Creates a new {@link HeaderList} initialized with the specified headers.
097    *
098    * @param headers
099    *    The headers to add to the list.
100    *    <br>Can be <jk>null</jk>.
101    *    <br><jk>null</jk> entries are ignored.
102    * @return A new unmodifiable instance, never <jk>null</jk>.
103    */
104   public static HeaderList of(List<Header> headers) {
105      return new HeaderList().append(headers);
106   }
107
108   /**
109    * Creates a new {@link HeaderList} initialized with the specified name/value pairs.
110    *
111    * <h5 class='figure'>Example</h5>
112    * <p class='bjava'>
113    *    HeaderList <jv>headers</jv> = HeaderList.<jsm>ofPairs</jsm>(<js>"Accept"</js>, <js>"text/xml"</js>, <js>"Content-Type"</js>, <js>"text/xml"</js>);
114    * </p>
115    *
116    * @param pairs
117    *    Initial list of pairs.
118    *    <br>Must be an even number of parameters representing key/value pairs.
119    * @throws RuntimeException If odd number of parameters were specified.
120    * @return A new instance.
121    */
122   public static HeaderList ofPairs(String...pairs) {
123      var x = new HeaderList();
124      if (pairs == null)
125         pairs = new String[0];
126      assertArg(pairs.length % 2 == 0, "Odd number of parameters passed into HeaderList.ofPairs()");
127      for (var i = 0; i < pairs.length; i += 2)
128         x.add(BasicHeader.of(pairs[i], pairs[i + 1]));
129      return x;
130   }
131
132   private VarResolver varResolver;
133   boolean caseSensitive;
134
135   /**
136    * Constructor.
137    */
138   public HeaderList() {
139      super(false);
140   }
141
142   /**
143    * Copy constructor.
144    *
145    * @param copyFrom The bean to copy.
146    */
147   protected HeaderList(HeaderList copyFrom) {
148      super(false, copyFrom);
149      caseSensitive = copyFrom.caseSensitive;
150   }
151
152   /**
153    * Adds the specified header to the end of the headers in this list.
154    *
155    * @param value The header to add.  <jk>null</jk> values are ignored.
156    * @return This object.
157    */
158   public HeaderList append(Header value) {
159      if (nn(value))
160         add(value);
161      return this;
162   }
163
164   /**
165    * Adds the specified headers to the end of the headers in this list.
166    *
167    * @param values The headers to add.  <jk>null</jk> values are ignored.
168    * @return This object.
169    */
170   public HeaderList append(Header...values) {
171      if (nn(values))
172         for (var value : values)
173            if (nn(value))
174               append(value);
175      return this;
176   }
177
178   /**
179    * Adds the specified headers to the end of the headers in this list.
180    *
181    * @param values The headers to add.  <jk>null</jk> values are ignored.
182    * @return This object.
183    */
184   public HeaderList append(List<Header> values) {
185      if (nn(values))
186         values.forEach(this::append);
187      return this;
188   }
189
190   /**
191    * Appends the specified header to the end of this list.
192    *
193    * <p>
194    * The header is added as a {@link BasicHeader}.
195    *
196    * @param name The header name.
197    * @param value The header value.
198    * @return This object.
199    */
200   public HeaderList append(String name, Object value) {
201      return append(createPart(name, value));
202   }
203
204   /**
205    * Appends the specified header to the end of this list using a value supplier.
206    *
207    * <p>
208    * The header is added as a {@link BasicHeader}.
209    *
210    * <p>
211    * Value is re-evaluated on each call to {@link BasicHeader#getValue()}.
212    *
213    * @param name The header name.
214    * @param value The header value supplier.
215    * @return This object.
216    */
217   public HeaderList append(String name, Supplier<?> value) {
218      return append(createPart(name, value));
219   }
220
221   /**
222    * Specifies that the headers in this list should be treated as case-sensitive.
223    *
224    * <p>
225    * The default behavior is case-insensitive.
226    *
227    * @param value The new value for this setting.
228    * @return This object.
229    */
230   public HeaderList caseSensitive(boolean value) {
231      assertModifiable();
232      caseSensitive = value;
233      return this;
234   }
235
236   /**
237    * Tests if headers with the given name are contained within this list.
238    *
239    * <p>
240    * Header name comparison is case insensitive.
241    *
242    * @param name The header name.
243    * @return <jk>true</jk> if at least one header with the name is present.
244    */
245   public boolean contains(String name) {
246      return stream().anyMatch(x -> eq(x.getName(), name));
247   }
248
249   /**
250    * Makes a copy of this list.
251    *
252    * @return A new copy of this list.
253    */
254   public HeaderList copy() {
255      return new HeaderList(this);
256   }
257
258   /**
259    * Performs an action on all matching headers in this list.
260    *
261    * <p>
262    * This is the preferred method for iterating over headers as it does not involve
263    * creation or copy of lists/arrays.
264    *
265    * @param filter A predicate to apply to each element to determine if it should be included.  Can be <jk>null</jk>.
266    * @param action An action to perform on each element.
267    * @return This object.
268    */
269   public HeaderList forEach(Predicate<Header> filter, Consumer<Header> action) {
270      forEach(x -> consumeIf(filter, action, x));
271      return this;
272   }
273
274   /**
275    * Performs an action on all headers with the specified name in this list.
276    *
277    * <p>
278    * This is the preferred method for iterating over headers as it does not involve
279    * creation or copy of lists/arrays.
280    *
281    * @param name The header name.
282    * @param action An action to perform on each element.
283    * @return This object.
284    */
285   public HeaderList forEach(String name, Consumer<Header> action) {
286      return forEach(x -> eq(name, x.getName()), action);
287   }
288
289   /**
290    * Performs an action on the values for all matching headers in this list.
291    *
292    * @param filter A predicate to apply to each element to determine if it should be included.  Can be <jk>null</jk>.
293    * @param action An action to perform on each element.
294    * @return This object.
295    */
296   public HeaderList forEachValue(Predicate<Header> filter, Consumer<String> action) {
297      return forEach(filter, x -> action.accept(x.getValue()));
298   }
299
300   /**
301    * Performs an action on the values of all matching headers in this list.
302    *
303    * @param name The header name.
304    * @param action An action to perform on each element.
305    * @return This object.
306    */
307   public HeaderList forEachValue(String name, Consumer<String> action) {
308      return forEach(name, x -> action.accept(x.getValue()));
309   }
310
311   /**
312    * Gets a header representing all of the header values with the given name.
313    *
314    * <p>
315    * Same as {@link #get(String, Class)} but the header name is pulled from the {@link org.apache.juneau.http.annotation.Header#name()} or
316    *    {@link org.apache.juneau.http.annotation.Header#value()} annotations.
317    *
318    * <h5 class='figure'>Example</h5>
319    * <p class='bjava'>
320    *    Age <jv>age</jv> = headerList.get(Age.<jk>class</jk>);
321    * </p>
322    *
323    * @param <T> The return type.
324    * @param type The header implementation class.
325    * @return A header with a condensed value or <jk>null</jk> if no headers by the given name are present
326    */
327   public <T> Optional<T> get(Class<T> type) {
328      assertArgNotNull("type", type);
329
330      String name = HeaderBeanMeta.of(type).getSchema().getName();
331      assertArg(nn(name), "Header name could not be found on bean type ''{0}''", cn(type));
332
333      return get(name, type);
334   }
335
336   /**
337    * Gets a header representing all of the header values with the given name.
338    *
339    * <p>
340    * If more that one header with the given name exists the values will be combined with <js>", "</js> as per
341    * <a href='https://tools.ietf.org/html/rfc2616#section-4.2'>RFC 2616 Section 4.2</a>.
342    *
343    * @param name The header name.
344    * @return A header with a condensed value, or {@link Optional#empty()} if no headers by the given name are present
345    */
346   public Optional<Header> get(String name) {
347
348      var first = (Header)null;
349      var rest = (List<Header>)null;
350      for (var x : this) {
351         if (eq(x.getName(), name)) {
352            if (first == null)
353               first = x;
354            else {
355               if (rest == null)
356                  rest = list();
357               rest.add(x);
358            }
359         }
360      }
361
362      if (first == null)
363         return opte();
364
365      if (rest == null)
366         return opt(first);
367
368      var sb = new CharArrayBuffer(128);
369      sb.append(first.getValue());
370      for (var element : rest) {
371         sb.append(", ");
372         sb.append(element.getValue());
373      }
374
375      return opt(new BasicHeader(name, sb.toString()));
376   }
377
378   /**
379    * Gets a header representing all of the header values with the given name.
380    *
381    * <p>
382    * If more that one header with the given name exists the values will be combined with <js>", "</js> as per
383    * <a href='https://tools.ietf.org/html/rfc2616#section-4.2'>RFC 2616 Section 4.2</a>.
384    *
385    * <p>
386    * The implementation class must have a public constructor taking in one of the following argument lists:
387    * <ul>
388    *    <li><c>X(String <jv>value</jv>)</c>
389    *    <li><c>X(Object <jv>value</jv>)</c>
390    *    <li><c>X(String <jv>name</jv>, String <jv>value</jv>)</c>
391    *    <li><c>X(String <jv>name</jv>, Object <jv>value</jv>)</c>
392    * </ul>
393    *
394    * <h5 class='figure'>Example</h5>
395    * <p class='bjava'>
396    *    BasicIntegerHeader <jv>age</jv> = headerList.get(<js>"Age"</js>, BasicIntegerHeader.<jk>class</jk>);
397    * </p>
398    *
399    * @param <T> The header implementation class.
400    * @param name The header name.
401    * @param type The header implementation class.
402    * @return A header with a condensed value or <jk>null</jk> if no headers by the given name are present
403    */
404   public <T> Optional<T> get(String name, Class<T> type) {
405
406      var first = (Header)null;
407      var rest = (List<Header>)null;
408      for (var x : this) {
409         if (eq(x.getName(), name)) {
410            if (first == null)
411               first = x;
412            else {
413               if (rest == null)
414                  rest = list();
415               rest.add(x);
416            }
417         }
418      }
419
420      if (first == null)
421         return opte();
422
423      if (rest == null)
424         return opt(HeaderBeanMeta.of(type).construct(name, first.getValue()));
425
426      var sb = new CharArrayBuffer(128);
427      sb.append(first.getValue());
428      for (var element : rest) {
429         sb.append(", ");
430         sb.append(element.getValue());
431      }
432
433      return opt(HeaderBeanMeta.of(type).construct(name, sb.toString()));
434   }
435
436   /**
437    * Gets all of the headers.
438    *
439    * <p>
440    * The returned array maintains the relative order in which the headers were added.
441    * Each call creates a new array not backed by this list.
442    *
443    * <p>
444    * As a general rule, it's more efficient to use the other methods with consumers to
445    * get headers.
446    *
447    * @return An array containing all headers, never <jk>null</jk>.
448    */
449   public Header[] getAll() { return stream().toArray(Header[]::new); }
450
451   /**
452    * Gets all of the headers with the given name.
453    *
454    * <p>
455    * The returned array maintains the relative order in which the headers were added.
456    * Header name comparison is case insensitive.
457    * Headers with null values are ignored.
458    * Each call creates a new array not backed by this list.
459    *
460    * <p>
461    * As a general rule, it's more efficient to use the other methods with consumers to
462    * get headers.
463    *
464    * @param name The header name.
465    *
466    * @return An array containing all matching headers, never <jk>null</jk>.
467    */
468   public Header[] getAll(String name) {
469      return stream().filter(x -> eq(x.getName(), name)).toArray(Header[]::new);
470   }
471
472   /**
473    * Gets the first header with the given name.
474    *
475    * <p>
476    * Header name comparison is case insensitive.
477    *
478    * @param name The header name.
479    * @return The first matching header, or {@link Optional#empty()} if not found.
480    */
481   public Optional<Header> getFirst(String name) {
482      for (var i = 0; i < size(); i++) {
483         var x = get(i);
484         if (eq(x.getName(), name))
485            return opt(x);
486      }
487      return opte();
488   }
489
490   /**
491    * Gets the last header with the given name.
492    *
493    * <p>
494    * Header name comparison is case insensitive.
495    *
496    * @param name The header name.
497    * @return The last matching header, or {@link Optional#empty()} if not found.
498    */
499   public Optional<Header> getLast(String name) {
500      for (var i = size() - 1; i >= 0; i--) {
501         var x = get(i);
502         if (eq(x.getName(), name))
503            return opt(x);
504      }
505      return opte();
506   }
507
508   /**
509    * Returns all the string values for all headers with the specified name.
510    *
511    * @param name The header name.
512    * @return An array containing all values.  Never <jk>null</jk>.
513    */
514   public String[] getValues(String name) {
515      return stream().filter(x -> eq(x.getName(), name)).map(Header::getValue).toArray(String[]::new);
516   }
517
518   /**
519    * Returns an iterator over this list of headers.
520    *
521    * @return A new iterator over this list of headers.
522    */
523   public HeaderIterator headerIterator() {
524      return new BasicHeaderIterator(toArray(new Header[0]), null, caseSensitive);
525   }
526
527   /**
528    * Returns an iterator over the headers with a given name in this list.
529    *
530    * @param name The name of the headers over which to iterate, or <jk>null</jk> for all headers
531    *
532    * @return A new iterator over the matching headers in this list.
533    */
534   public HeaderIterator headerIterator(String name) {
535      return new BasicHeaderIterator(getAll(name), name, caseSensitive);
536   }
537
538   /**
539    * Adds the specified header to the beginning of the headers in this list.
540    *
541    * @param value The header to add.  <jk>null</jk> values are ignored.
542    * @return This object.
543    */
544   public HeaderList prepend(Header value) {
545      if (nn(value))
546         add(0, value);
547      return this;
548   }
549
550   /**
551    * Adds the specified headers to the beginning of the headers in this list.
552    *
553    * @param values The headers to add.  <jk>null</jk> values are ignored.
554    * @return This object.
555    */
556   public HeaderList prepend(Header...values) {
557      if (nn(values))
558         prepend(l(values));
559      return this;
560   }
561
562   /**
563    * Adds the specified headers to the beginning of the headers in this list.
564    *
565    * @param values The headers to add.  <jk>null</jk> values are ignored.
566    * @return This object.
567    */
568   public HeaderList prepend(List<Header> values) {
569      if (nn(values))
570         addAll(0, values);
571      return this;
572   }
573
574   /**
575    * Appends the specified header to the beginning of this list.
576    *
577    * <p>
578    * The header is added as a {@link BasicHeader}.
579    *
580    * @param name The header name.
581    * @param value The header value.
582    * @return This object.
583    */
584   public HeaderList prepend(String name, Object value) {
585      return prepend(createPart(name, value));
586   }
587
588   /**
589    * Appends the specified header to the beginning of this list using a value supplier.
590    *
591    * <p>
592    * The header is added as a {@link BasicHeader}.
593    *
594    * <p>
595    * Value is re-evaluated on each call to {@link BasicHeader#getValue()}.
596    *
597    * @param name The header name.
598    * @param value The header value supplier.
599    * @return This object.
600    */
601   public HeaderList prepend(String name, Supplier<?> value) {
602      return prepend(createPart(name, value));
603   }
604
605   /**
606    * Removes the specified header from this list.
607    *
608    * @param value The header to remove.  <jk>null</jk> values are ignored.
609    * @return This object.
610    */
611   public HeaderList remove(Header value) {
612      if (nn(value))
613         removeIf(x -> eq(x.getName(), value.getName()) && eq(x.getValue(), value.getValue()));
614      return this;
615   }
616
617   /**
618    * Removes the specified headers from this list.
619    *
620    * @param values The headers to remove.  <jk>null</jk> values are ignored.
621    * @return This object.
622    */
623   public HeaderList remove(Header...values) {
624      for (var value : values)
625         remove(value);
626      return this;
627   }
628
629   /**
630    * Removes the specified headers from this list.
631    *
632    * @param values The headers to remove.  <jk>null</jk> values are ignored.
633    * @return This object.
634    */
635   public HeaderList remove(List<Header> values) {
636      if (nn(values))
637         values.forEach(this::remove);
638      return this;
639   }
640
641   /**
642    * Removes the header with the specified name from this list.
643    *
644    * @param name The header name.
645    * @return This object.
646    */
647   public HeaderList remove(String name) {
648      removeIf(x -> eq(x.getName(), name));
649      return this;
650   }
651
652   /**
653    * Removes the header with the specified name from this list.
654    *
655    * @param names The header name.
656    * @return This object.
657    */
658   public HeaderList remove(String...names) {
659      if (nn(names))
660         for (var name : names)
661            remove(name);
662      return this;
663   }
664
665   /**
666    * Removes all headers from this list.
667    *
668    * @return This object.
669    */
670   public HeaderList removeAll() {
671      clear();
672      return this;
673   }
674
675   /**
676    * Allows header values to contain SVL variables.
677    *
678    * <p>
679    * Resolves variables in header 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&lt;?&gt;)}
683    *    <li class='jm'>{@link #prepend(String, Object) prepend(String,Object)}
684    *    <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier&lt;?&gt;)}
685    *    <li class='jm'>{@link #set(String, Object) set(String,Object)}
686    *    <li class='jm'>{@link #set(String, Supplier) set(String,Supplier&lt;?&gt;)}
687    * </ul>
688    *
689    * <p>
690    * Uses {@link VarResolver#DEFAULT} to resolve variables.
691    *
692    * @return This object.
693    */
694   public HeaderList resolving() {
695      return resolving(VarResolver.DEFAULT);
696   }
697
698   /**
699    * Allows header values to contain SVL variables.
700    *
701    * <p>
702    * Resolves variables in header values when using the following methods:
703    * <ul>
704    *    <li class='jm'>{@link #append(String, Object) append(String,Object)}
705    *    <li class='jm'>{@link #append(String, Supplier) append(String,Supplier&lt;?&gt;)}
706    *    <li class='jm'>{@link #prepend(String, Object) prepend(String,Object)}
707    *    <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier&lt;?&gt;)}
708    *    <li class='jm'>{@link #set(String, Object) set(String,Object)}
709    *    <li class='jm'>{@link #set(String, Supplier) set(String,Supplier&lt;?&gt;)}
710    * </ul>
711    *
712    * @param varResolver The variable resolver to use for resolving variables.
713    * @return This object.
714    */
715   public HeaderList resolving(VarResolver varResolver) {
716      assertModifiable();
717      this.varResolver = varResolver;
718      return this;
719   }
720
721   /**
722    * Adds or replaces the header(s) with the same name.
723    *
724    * <p>
725    * If no header with the same name is found the given header is added to the end of the list.
726    *
727    * @param value The headers to replace.  <jk>null</jk> values are ignored.
728    * @return This object.
729    */
730   public HeaderList set(Header value) {
731      if (nn(value)) {
732         var replaced = false;
733         for (int i = 0, j = size(); i < j; i++) {
734            var x = get(i);
735            if (eq(x.getName(), value.getName())) {
736               if (replaced) {
737                  remove(i);
738                  j--;
739               } else {
740                  set(i, value);
741                  replaced = true;
742               }
743            }
744         }
745
746         if (! replaced)
747            add(value);
748      }
749
750      return this;
751   }
752
753   /**
754    * Adds or replaces the header(s) with the same name.
755    *
756    * <p>
757    * If no header with the same name is found the given header is added to the end of the list.
758    *
759    * @param values The headers to replace.  <jk>null</jk> values are ignored.
760    * @return This object.
761    */
762   public HeaderList set(Header...values) {
763      if (nn(values))
764         set(l(values));
765      return this;
766   }
767
768   /**
769    * Replaces the first occurrence of the headers with the same name.
770    *
771    * <p>
772    * If no header with the same name is found the given header is added to the end of the list.
773    *
774    * @param values The headers to replace.  <jk>null</jk> values are ignored.
775    * @return This object.
776    */
777   public HeaderList set(List<Header> values) {
778
779      if (nn(values)) {
780         for (var h : values) {
781            if (nn(h)) {
782               for (int i2 = 0, j2 = size(); i2 < j2; i2++) {
783                  var x = get(i2);
784                  if (eq(x.getName(), h.getName())) {
785                     remove(i2);
786                     j2--;
787                  }
788               }
789            }
790         }
791
792         for (var x : values) {
793            if (nn(x)) {
794               add(x);
795            }
796         }
797      }
798
799      return this;
800   }
801
802   /**
803    * Replaces the first occurrence of the headers with the same name.
804    *
805    * @param name The header name.
806    * @param value The header value.
807    * @return This object.
808    */
809   public HeaderList set(String name, Object value) {
810      return set(createPart(name, value));
811   }
812
813   /**
814    * Replaces the first occurrence of the headers with the same name.
815    *
816    * @param name The header name.
817    * @param value The header value.
818    * @return This object.
819    */
820   public HeaderList set(String name, Supplier<?> value) {
821      return set(createPart(name, value));
822   }
823
824   /**
825    * Makes a copy of this list of headers and adds a collection of default headers.
826    *
827    * <p>
828    * Default headers are set if they're not already in the list.
829    *
830    * @param headers The list of default headers.
831    * @return A new list, or the same list if the headers were empty.
832    */
833   public HeaderList setDefault(Header...headers) {
834      if (nn(headers))
835         setDefault(l(headers));
836      return this;
837   }
838
839   /**
840    * Adds a collection of default headers.
841    *
842    * <p>
843    * Default headers are set if they're not already in the list.
844    *
845    * @param headers The list of default headers.
846    * @return This object.
847    */
848   public HeaderList setDefault(List<Header> headers) {
849      if (nn(headers))
850         headers.stream().filter(x -> nn(x) && ! contains(x.getName())).forEach(this::set);
851      return this;
852   }
853
854   /**
855    * Replaces the first occurrence of the headers with the same name.
856    *
857    * @param name The header name.
858    * @param value The header value.
859    * @return This object.
860    */
861   public HeaderList setDefault(String name, Object value) {
862      return setDefault(createPart(name, value));
863   }
864
865   /**
866    * Replaces the first occurrence of the headers with the same name.
867    *
868    * @param name The header name.
869    * @param value The header value.
870    * @return This object.
871    */
872   public HeaderList setDefault(String name, Supplier<?> value) {
873      return setDefault(createPart(name, value));
874   }
875
876   @Override /* Overridden from ControlledArrayList */
877   public HeaderList setUnmodifiable() {
878      super.setUnmodifiable();
879      return this;
880   }
881
882   /**
883    * Returns a stream of the headers in this list with the specified name.
884    *
885    * <p>
886    * This does not involve a copy of the underlying array of <c>Header</c> objects so should perform well.
887    *
888    * @param name The header name.
889    * @return This object.
890    */
891   public Stream<Header> stream(String name) {
892      return stream().filter(x -> eq(name, x.getName()));
893   }
894
895   @Override /* Overridden from Object */
896   public String toString() {
897      return "[" + join(this, ", ") + "]";
898   }
899
900   /**
901    * Creates a new header out of the specified name/value pair.
902    *
903    * @param name The header name.
904    * @param value The header value.
905    * @return A new header.
906    */
907   private Header createPart(String name, Object value) {
908      var isResolving = nn(varResolver);
909
910      if (value instanceof Supplier<?> value2) {
911         return isResolving ? new BasicHeader(name, resolver(value2)) : new BasicHeader(name, value2);
912      }
913      return isResolving ? new BasicHeader(name, resolver(value)) : new BasicHeader(name, value);
914   }
915
916   private boolean eq(String s1, String s2) {
917      return Utils.eq(! caseSensitive, s1, s2);  // NOAI
918   }
919
920   private Supplier<Object> resolver(Object input) {
921      return () -> varResolver.resolve(s(unwrap(input)));
922   }
923
924   private static Object unwrap(Object o) {
925      while (o instanceof Supplier<?> s)
926         o = s.get();
927      return o;
928   }
929}