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.Shorts.*;
020
021import java.util.*;
022import java.util.function.*;
023
024import org.apache.juneau.http.*;
025
026/**
027 * A mutable, ordered list of {@link HttpPart} instances.
028 *
029 * <p>
030 * Mirrors the semantics of {@code PartList} from {@code juneau-rest-common-classic} without the
031 * Apache HttpCore dependency. Part-name comparisons are case-sensitive by default (matching the
032 * classic default for parts; headers use case-insensitive comparisons).
033 *
034 * <p>
035 * Use {@link PartList} when you need an immutable list bound to an HTTP body (e.g.
036 * {@code application/x-www-form-urlencoded}); use {@code HttpPartList} when you need a mutable
037 * builder for default form data / query parameter lists.
038 *
039 * <h5 class='section'>See Also:</h5><ul>
040 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
041 * </ul>
042 *
043 * @since 10.0.0
044 */
045@SuppressWarnings({
046   "java:S2160" // equals() inherited from ArrayList; list equality is element-based which is correct
047})
048public class HttpPartList extends ArrayList<HttpPart> {
049
050   private static final long serialVersionUID = 1L;
051
052   /** Represents no part list in annotations. */
053   public static final class Void extends HttpPartList {
054      private static final long serialVersionUID = 1L;
055   }
056
057   private boolean caseInsensitive;
058
059   /**
060    * Creates a new empty {@link HttpPartList}.
061    *
062    * @return A new instance. Never <jk>null</jk>.
063    */
064   public static HttpPartList create() {
065      return new HttpPartList();
066   }
067
068   /**
069    * Creates a new {@link HttpPartList} initialized with the given parts.
070    *
071    * @param parts The parts to include. {@code null} entries are ignored.
072    * @return A new instance. Never <jk>null</jk>.
073    */
074   public static HttpPartList of(HttpPart...parts) {
075      return new HttpPartList().append(parts);
076   }
077
078   /**
079    * Creates a new {@link HttpPartList} initialized with the given parts.
080    *
081    * @param parts The parts to include. {@code null} entries are ignored.
082    * @return A new instance. Never <jk>null</jk>.
083    */
084   public static HttpPartList of(List<HttpPart> parts) {
085      return new HttpPartList().append(parts);
086   }
087
088   /**
089    * Creates a new {@link HttpPartList} initialized with the given alternating {@code name}/{@code value} pairs.
090    *
091    * @param pairs Alternating name/value strings. Can be <jk>null</jk> (treated as empty). Length must be even.
092    * @return A new instance. Never <jk>null</jk>.
093    * @throws IllegalArgumentException If the number of arguments is odd.
094    */
095   public static HttpPartList ofPairs(String...pairs) {
096      var x = new HttpPartList();
097      if (pairs == null)
098         pairs = new String[0];
099      req(pairs.length % 2 == 0, "Odd number of parameters passed into HttpPartList.ofPairs()");
100      for (var i = 0; i < pairs.length; i += 2)
101         x.add(HttpPartBean.of(pairs[i], pairs[i + 1]));
102      return x;
103   }
104
105   /** Constructor. */
106   public HttpPartList() {
107      super();
108   }
109
110   /**
111    * Copy constructor.
112    *
113    * @param copyFrom The list to copy. Must not be <jk>null</jk>.
114    */
115   protected HttpPartList(HttpPartList copyFrom) {
116      super(copyFrom);
117      this.caseInsensitive = copyFrom.caseInsensitive;
118   }
119
120   /**
121    * Sets whether part-name comparisons are case-insensitive. Defaults to {@code false} (case-sensitive).
122    *
123    * @param value New value.
124    * @return This object.
125    */
126   public HttpPartList caseInsensitive(boolean value) {
127      this.caseInsensitive = value;
128      return this;
129   }
130
131   /**
132    * Appends the given part.
133    *
134    * @param value The part to add. {@code null} is ignored.
135    * @return This object.
136    */
137   public HttpPartList append(HttpPart value) {
138      if (value != null)
139         add(value);
140      return this;
141   }
142
143   /**
144    * Appends the given parts in order.
145    *
146    * @param values The parts to add. {@code null} entries are ignored.
147    * @return This object.
148    */
149   public HttpPartList append(HttpPart...values) {
150      if (values != null)
151         for (var v : values)
152            append(v);
153      return this;
154   }
155
156   /**
157    * Appends the given parts in order.
158    *
159    * @param values The parts to add. {@code null} entries are ignored.
160    * @return This object.
161    */
162   public HttpPartList append(List<HttpPart> values) {
163      if (values != null)
164         values.forEach(this::append);
165      return this;
166   }
167
168   /**
169    * Appends a part constructed from the given name and string value.
170    *
171    * @param name The part name. Must not be <jk>null</jk>.
172    * @param value The part value. Can be <jk>null</jk> to omit the part from the request.
173    * @return This object.
174    */
175   public HttpPartList append(String name, String value) {
176      return append(HttpPartBean.of(name, value));
177   }
178
179   /**
180    * Sets the given part as a default. If a part with the same name already exists, this is a no-op
181    * (first-in-chain wins per name).
182    *
183    * @param value The part to set. {@code null} is ignored.
184    * @return This object.
185    */
186   public HttpPartList setDefault(HttpPart value) {
187      if (value != null && !contains(value.getName()))
188         add(value);
189      return this;
190   }
191
192   /**
193    * Sets the given parts as defaults, in order. For each, if a part with the same name already exists,
194    * the entry is skipped (first-in-chain wins per name).
195    *
196    * @param values The parts to set. {@code null} entries are ignored.
197    * @return This object.
198    */
199   public HttpPartList setDefault(HttpPart...values) {
200      if (values != null)
201         for (var v : values)
202            setDefault(v);
203      return this;
204   }
205
206   /**
207    * Sets a default part with the given name and value (first-in-chain wins per name).
208    *
209    * @param name The part name. Must not be <jk>null</jk>.
210    * @param value The part value. Can be <jk>null</jk> to omit the part from the request.
211    * @return This object.
212    */
213   public HttpPartList setDefault(String name, String value) {
214      return setDefault(HttpPartBean.of(name, value));
215   }
216
217   /**
218    * Sets the given part, replacing any prior entry with the same name.
219    *
220    * @param value The part to set. {@code null} is ignored.
221    * @return This object.
222    */
223   public HttpPartList set(HttpPart value) {
224      if (value == null)
225         return this;
226      removeAll(value.getName());
227      add(value);
228      return this;
229   }
230
231   /**
232    * Sets the given parts, each replacing any prior entry with the same name.
233    *
234    * @param values The parts to set. {@code null} entries are ignored.
235    * @return This object.
236    */
237   public HttpPartList set(HttpPart...values) {
238      if (values != null)
239         for (var v : values)
240            set(v);
241      return this;
242   }
243
244   /**
245    * Sets a part with the given name and value, replacing any prior entry with the same name.
246    *
247    * @param name The part name. Must not be <jk>null</jk>.
248    * @param value The part value. Can be <jk>null</jk> to omit the part from the request.
249    * @return This object.
250    */
251   public HttpPartList set(String name, String value) {
252      return set(HttpPartBean.of(name, value));
253   }
254
255   /**
256    * Removes all parts with the given name.
257    *
258    * @param name The part name. Must not be <jk>null</jk>.
259    * @return This object.
260    */
261   public HttpPartList removeAll(String name) {
262      reqnn("name", name);
263      removeIf(p -> nameMatches(p, name));
264      return this;
265   }
266
267   /**
268    * Returns {@code true} if a part with the given name is present.
269    *
270    * @param name The part name.
271    * @return {@code true} if present.
272    */
273   public boolean contains(String name) {
274      return stream().anyMatch(p -> nameMatches(p, name));
275   }
276
277   /**
278    * Returns the first part with the given name, or {@code null} if absent.
279    *
280    * @param name The part name.
281    * @return The first matching part, or {@code null}.
282    */
283   public HttpPart getFirst(String name) {
284      return stream().filter(p -> nameMatches(p, name)).findFirst().orElse(null);
285   }
286
287   /**
288    * Returns the last part with the given name, or {@code null} if absent.
289    *
290    * @param name The part name.
291    * @return The last matching part, or {@code null}.
292    */
293   public HttpPart getLast(String name) {
294      HttpPart last = null;
295      for (var p : this)
296         if (nameMatches(p, name))
297            last = p;
298      return last;
299   }
300
301   /**
302    * Returns all parts with the given name in insertion order.
303    *
304    * @param name The part name.
305    * @return A new list of matching parts. Never <jk>null</jk>.
306    */
307   public List<HttpPart> getAll(String name) {
308      return stream().filter(p -> nameMatches(p, name)).toList();
309   }
310
311   /**
312    * Returns all parts in this list as a typed array.
313    *
314    * @return A new array. Never <jk>null</jk>.
315    */
316   public HttpPart[] getAll() {
317      return toPartArray();
318   }
319
320   /**
321    * Performs an action on each part whose name matches.
322    *
323    * @param name The part name.
324    * @param action The action.
325    * @return This object.
326    */
327   public HttpPartList forEach(String name, Consumer<HttpPart> action) {
328      for (var p : this)
329         if (nameMatches(p, name))
330            action.accept(p);
331      return this;
332   }
333
334   /**
335    * Returns a copy of this list.
336    *
337    * @return A new copy. Never <jk>null</jk>.
338    */
339   public HttpPartList copy() {
340      return new HttpPartList(this);
341   }
342
343   /**
344    * Returns an unmodifiable snapshot of this list.
345    *
346    * <p>
347    * Idempotency: if this list is already an {@link Unmodifiable} snapshot, it is returned as-is.
348    *
349    * @return An unmodifiable snapshot of this list, or this list if it is already unmodifiable.
350    */
351   public HttpPartList unmodifiable() {
352      return this instanceof Unmodifiable ? this : new Unmodifiable(this);
353   }
354
355   /**
356    * Returns <jk>true</jk> if this list is an unmodifiable snapshot.
357    *
358    * @return <jk>true</jk> if this list is an {@link UnmodifiableBean} snapshot.
359    */
360   public boolean isUnmodifiable() {
361      return this instanceof UnmodifiableBean;
362   }
363
364   /**
365    * Returns the parts in this list as a typed array.
366    *
367    * @return A new array. Never <jk>null</jk>.
368    */
369   public HttpPart[] toPartArray() {
370      return toArray(new HttpPart[0]);
371   }
372
373   private boolean nameMatches(HttpPart p, String name) {
374      return caseInsensitive ? eqic(p.getName(), name) : eq(p.getName(), name);
375   }
376
377   /**
378    * An unmodifiable snapshot of a {@link HttpPartList}.
379    *
380    * <p>
381    * This is the "collection-mutator-override" variant of the immutability paradigm.  Because {@link HttpPartList}
382    * extends {@link ArrayList}, a single funnel cannot intercept the inherited {@code ArrayList} mutators, so this
383    * subclass snapshot-copies the elements in its constructor and overrides the entire collection-mutator surface — its
384    * own fluent mutators plus the inherited {@code ArrayList} mutators (including the {@code iterator()} /
385    * {@code listIterator()} mutators) — to throw {@link UnsupportedOperationException}.  Read operations and
386    * non-mutating iteration continue to work.
387    */
388   public static class Unmodifiable extends HttpPartList implements UnmodifiableBean {
389
390      private static final long serialVersionUID = 1L;
391
392      /**
393       * Constructor.
394       *
395       * @param copyFrom The list to snapshot-copy. Must not be <jk>null</jk>.
396       */
397      Unmodifiable(HttpPartList copyFrom) {
398         super(copyFrom);
399      }
400
401      // --- Fluent mutators -----------------------------------------------------------------------------------------
402
403      @Override public HttpPartList append(HttpPart value) { throw uoex(RO); }
404      @Override public HttpPartList append(HttpPart...values) { throw uoex(RO); }
405      @Override public HttpPartList append(List<HttpPart> values) { throw uoex(RO); }
406      @Override public HttpPartList append(String name, String value) { throw uoex(RO); }
407      @Override public HttpPartList caseInsensitive(boolean value) { throw uoex(RO); }
408      @Override public HttpPartList removeAll(String name) { throw uoex(RO); }
409      @Override public HttpPartList set(HttpPart value) { throw uoex(RO); }
410      @Override public HttpPartList set(HttpPart...values) { throw uoex(RO); }
411      @Override public HttpPartList set(String name, String value) { throw uoex(RO); }
412      @Override public HttpPartList setDefault(HttpPart value) { throw uoex(RO); }
413      @Override public HttpPartList setDefault(HttpPart...values) { throw uoex(RO); }
414      @Override public HttpPartList setDefault(String name, String value) { throw uoex(RO); }
415
416      // --- Inherited ArrayList collection mutators -----------------------------------------------------------------
417
418      @Override public boolean add(HttpPart e) { throw uoex(RO); }
419      @Override public void add(int index, HttpPart element) { throw uoex(RO); }
420      @Override public boolean addAll(Collection<? extends HttpPart> c) { throw uoex(RO); }
421      @Override public boolean addAll(int index, Collection<? extends HttpPart> c) { throw uoex(RO); }
422      @Override public void clear() { throw uoex(RO); }
423      @Override public boolean remove(Object o) { throw uoex(RO); }
424      @Override public HttpPart remove(int index) { throw uoex(RO); }
425      @Override public boolean removeAll(Collection<?> c) { throw uoex(RO); }
426      @Override public boolean removeIf(Predicate<? super HttpPart> filter) { throw uoex(RO); }
427      @Override public void replaceAll(UnaryOperator<HttpPart> operator) { throw uoex(RO); }
428      @Override public boolean retainAll(Collection<?> c) { throw uoex(RO); }
429      @Override public HttpPart set(int index, HttpPart element) { throw uoex(RO); }
430      @Override public void sort(Comparator<? super HttpPart> c) { throw uoex(RO); }
431
432      @Override public Iterator<HttpPart> iterator() {
433         var i = super.iterator();
434         return new Iterator<>() {
435            @Override public boolean hasNext() { return i.hasNext(); }
436            @Override public HttpPart next() { return i.next(); }
437            @Override public void remove() { throw uoex(RO); }
438         };
439      }
440
441      @Override public ListIterator<HttpPart> listIterator() { return listIterator(0); }
442
443      @Override public ListIterator<HttpPart> listIterator(int index) {
444         var i = super.listIterator(index);
445         return new ListIterator<>() {
446            @Override public boolean hasNext() { return i.hasNext(); }
447            @Override public HttpPart next() { return i.next(); }
448            @Override public boolean hasPrevious() { return i.hasPrevious(); }
449            @Override public HttpPart previous() { return i.previous(); }
450            @Override public int nextIndex() { return i.nextIndex(); }
451            @Override public int previousIndex() { return i.previousIndex(); }
452            @Override public void add(HttpPart e) { throw uoex(RO); }
453            @Override public void set(HttpPart e) { throw uoex(RO); }
454            @Override public void remove() { throw uoex(RO); }
455         };
456      }
457   }
458
459   private static final String RO = "Bean is unmodifiable.";
460}