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}