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