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