View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.http.message;
29  
30  import java.io.Serializable;
31  import java.util.ArrayList;
32  import java.util.Collections;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Locale;
36  
37  import org.apache.hc.core5.http.Header;
38  import org.apache.hc.core5.http.MessageHeaders;
39  import org.apache.hc.core5.http.ProtocolException;
40  import org.apache.hc.core5.util.CharArrayBuffer;
41  import org.apache.hc.core5.util.LangUtils;
42  
43  /**
44   * A class for combining a set of headers. This class allows for multiple headers with the same name
45   * and keeps track of the order in which headers were added.
46   *
47   * @since 4.0
48   */
49  public class HeaderGroup implements MessageHeaders, Serializable {
50  
51      private static final long serialVersionUID = 2608834160639271617L;
52  
53      private static final Headerp/Header.html#Header">Header[] EMPTY = new Header[] {};
54  
55      /** The list of headers for this group, in the order in which they were added */
56      private final List<Header> headers;
57  
58  
59      /**
60       * Constructor for HeaderGroup.
61       */
62      public HeaderGroup() {
63          this.headers = new ArrayList<>(16);
64      }
65  
66      /**
67       * Removes all headers.
68       */
69      public void clear() {
70          headers.clear();
71      }
72  
73      /**
74       * Adds the given header to the group.  The order in which this header was
75       * added is preserved.
76       *
77       * @param header the header to add
78       */
79      public void addHeader(final Header header) {
80          if (header == null) {
81              return;
82          }
83          headers.add(header);
84      }
85  
86      /**
87       * Removes the first given header.
88       *
89       * @param header the header to remove
90       * @return <code>true</code> if a header was removed as a result of this call.
91       */
92      public boolean removeHeader(final Header header) {
93          if (header == null) {
94              return false;
95          }
96          for (int i = 0; i < this.headers.size(); i++) {
97              final Header current = this.headers.get(i);
98              if (headerEquals(header, current)) {
99                  this.headers.remove(current);
100                 return true;
101             }
102         }
103         return false;
104     }
105 
106     private boolean headerEquals(final Header/Header.html#Header">Header header1, final Header header2) {
107         return header2 == header1 || header2.getName().equalsIgnoreCase(header1.getName())
108                 && LangUtils.equals(header1.getValue(), header2.getValue());
109     }
110 
111     /**
112      * Removes all headers that match the given header.
113      *
114      * @param header the header to remove
115      * @return <code>true</code> if any header was removed as a result of this call.
116      *
117      * @since 5.0
118      */
119     public boolean removeHeaders(final Header header) {
120         if (header == null) {
121             return false;
122         }
123         boolean removed = false;
124         for (final Iterator<Header> iterator = headerIterator(); iterator.hasNext();) {
125             final Header current = iterator.next();
126             if (headerEquals(header, current)) {
127                 iterator.remove();
128                 removed = true;
129             }
130         }
131         return removed;
132     }
133 
134     /**
135      * Replaces the first occurrence of the header with the same name. If no header with
136      * the same name is found the given header is added to the end of the list.
137      *
138      * @param header the new header that should replace the first header with the same
139      * name if present in the list.
140      *
141      * @since 5.0
142      */
143     public void setHeader(final Header header) {
144         if (header == null) {
145             return;
146         }
147         for (int i = 0; i < this.headers.size(); i++) {
148             final Header current = this.headers.get(i);
149             if (current.getName().equalsIgnoreCase(header.getName())) {
150                 this.headers.set(i, header);
151                 return;
152             }
153         }
154         this.headers.add(header);
155     }
156 
157     /**
158      * Sets all of the headers contained within this group overriding any
159      * existing headers. The headers are added in the order in which they appear
160      * in the array.
161      *
162      * @param headers the headers to set
163      */
164     public void setHeaders(final Header... headers) {
165         clear();
166         if (headers == null) {
167             return;
168         }
169         Collections.addAll(this.headers, headers);
170     }
171 
172     /**
173      * Gets a header representing all of the header values with the given name.
174      * If more that one header with the given name exists the values will be
175      * combined with a ",".
176      *
177      * <p>Header name comparison is case insensitive.
178      *
179      * @param name the name of the header(s) to get
180      * @return a header with a condensed value or {@code null} if no
181      * headers by the given name are present
182      */
183     public Header getCondensedHeader(final String name) {
184         final Header[] hdrs = getHeaders(name);
185 
186         if (hdrs.length == 0) {
187             return null;
188         } else if (hdrs.length == 1) {
189             return hdrs[0];
190         } else {
191             final CharArrayBufferffer.html#CharArrayBuffer">CharArrayBuffer valueBuffer = new CharArrayBuffer(128);
192             valueBuffer.append(hdrs[0].getValue());
193             for (int i = 1; i < hdrs.length; i++) {
194                 valueBuffer.append(", ");
195                 valueBuffer.append(hdrs[i].getValue());
196             }
197 
198             return new BasicHeader(name.toLowerCase(Locale.ROOT), valueBuffer.toString());
199         }
200     }
201 
202     /**
203      * Gets all of the headers with the given name.  The returned array
204      * maintains the relative order in which the headers were added.
205      *
206      * <p>Header name comparison is case insensitive.
207      *
208      * @param name the name of the header(s) to get
209      *
210      * @return an array of length &ge; 0
211      */
212     @Override
213     public Header[] getHeaders(final String name) {
214         List<Header> headersFound = null;
215         for (int i = 0; i < this.headers.size(); i++) {
216             final Header header = this.headers.get(i);
217             if (header.getName().equalsIgnoreCase(name)) {
218                 if (headersFound == null) {
219                     headersFound = new ArrayList<>();
220                 }
221                 headersFound.add(header);
222             }
223         }
224         return headersFound != null ? headersFound.toArray(EMPTY) : EMPTY;
225     }
226 
227     /**
228      * Gets the first header with the given name.
229      *
230      * <p>Header name comparison is case insensitive.
231      *
232      * @param name the name of the header to get
233      * @return the first header or {@code null}
234      */
235     @Override
236     public Header getFirstHeader(final String name) {
237         for (int i = 0; i < this.headers.size(); i++) {
238             final Header header = this.headers.get(i);
239             if (header.getName().equalsIgnoreCase(name)) {
240                 return header;
241             }
242         }
243         return null;
244     }
245 
246     /**
247      * Gets single first header with the given name.
248      *
249      * <p>Header name comparison is case insensitive.
250      *
251      * @param name the name of the header to get
252      * @return the first header or {@code null}
253      * @throws ProtocolException in case multiple headers with the given name are found.
254      */
255     @Override
256     public Header getHeader(final String name) throws ProtocolException {
257         int count = 0;
258         Header singleHeader = null;
259         for (int i = 0; i < this.headers.size(); i++) {
260             final Header header = this.headers.get(i);
261             if (header.getName().equalsIgnoreCase(name)) {
262                 singleHeader = header;
263                 count++;
264             }
265         }
266         if (count > 1) {
267             throw new ProtocolException("multiple '%s' headers found", name);
268         }
269         return singleHeader;
270     }
271 
272     /**
273      * Gets the last header with the given name.
274      *
275      * <p>Header name comparison is case insensitive.
276      *
277      * @param name the name of the header to get
278      * @return the last header or {@code null}
279      */
280     @Override
281     public Header getLastHeader(final String name) {
282         // start at the end of the list and work backwards
283         for (int i = headers.size() - 1; i >= 0; i--) {
284             final Header header = headers.get(i);
285             if (header.getName().equalsIgnoreCase(name)) {
286                 return header;
287             }
288         }
289 
290         return null;
291     }
292 
293     /**
294      * Gets all of the headers contained within this group.
295      *
296      * @return an array of length &ge; 0
297      */
298     @Override
299     public Header[] getHeaders() {
300         return headers.toArray(EMPTY);
301     }
302 
303     /**
304      * Tests if headers with the given name are contained within this group.
305      *
306      * <p>Header name comparison is case insensitive.
307      *
308      * @param name the header name to test for
309      * @return {@code true} if at least one header with the name is
310      * contained, {@code false} otherwise
311      */
312     @Override
313     public boolean containsHeader(final String name) {
314         for (int i = 0; i < this.headers.size(); i++) {
315             final Header header = this.headers.get(i);
316             if (header.getName().equalsIgnoreCase(name)) {
317                 return true;
318             }
319         }
320 
321         return false;
322     }
323 
324     /**
325      * Checks if a certain header is present in this message and how many times.
326      * <p>Header name comparison is case insensitive.
327      *
328      * @param name the header name to check for.
329      * @return number of occurrences of the header in the message.
330      */
331     @Override
332     public int countHeaders(final String name) {
333         int count = 0;
334         for (int i = 0; i < this.headers.size(); i++) {
335             final Header header = this.headers.get(i);
336             if (header.getName().equalsIgnoreCase(name)) {
337                 count++;
338             }
339         }
340         return count;
341     }
342 
343     /**
344      * Returns an iterator over this group of headers.
345      *
346      * @return iterator over this group of headers.
347      *
348      * @since 5.0
349      */
350     @Override
351     public Iterator<Header> headerIterator() {
352         return new BasicListHeaderIterator(this.headers, null);
353     }
354 
355     /**
356      * Returns an iterator over the headers with a given name in this group.
357      *
358      * @param name      the name of the headers over which to iterate, or
359      *                  {@code null} for all headers
360      *
361      * @return iterator over some headers in this group.
362      *
363      * @since 5.0
364      */
365     @Override
366     public Iterator<Header> headerIterator(final String name) {
367         return new BasicListHeaderIterator(this.headers, name);
368     }
369 
370     /**
371      * Removes all headers with a given name in this group.
372      *
373      * @param name      the name of the headers to be removed.
374      * @return <code>true</code> if any header was removed as a result of this call.
375      *
376      * @since 5.0
377      */
378     public boolean removeHeaders(final String name) {
379         if (name == null) {
380             return false;
381         }
382         boolean removed = false;
383         for (final Iterator<Header> iterator = headerIterator(); iterator.hasNext(); ) {
384             final Header header = iterator.next();
385             if (header.getName().equalsIgnoreCase(name)) {
386                 iterator.remove();
387                 removed = true;
388             }
389         }
390         return removed;
391     }
392 
393     @Override
394     public String toString() {
395         return this.headers.toString();
396     }
397 
398 }