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.
45   * This class allows for multiple headers with the same name and
46   * keeps track of the order in which headers were added.
47   *
48   *
49   * @since 4.0
50   */
51  public class HeaderGroup implements MessageHeaders, Serializable {
52  
53  
54      // HTTPCORE-361 : we don't use the for-each syntax, when iterating headers
55      // as that creates an Iterator that needs to be garbage-collected
56  
57      private static final long serialVersionUID = 2608834160639271617L;
58  
59      private final Header[] EMPTY = new Header[] {};
60  
61      /** The list of headers for this group, in the order in which they were added */
62      private final List<Header> headers;
63  
64      /**
65       * Constructor for HeaderGroup.
66       */
67      public HeaderGroup() {
68          this.headers = new ArrayList<>(16);
69      }
70  
71      /**
72       * Removes any contained headers.
73       */
74      public void clear() {
75          headers.clear();
76      }
77  
78      /**
79       * Adds the given header to the group.  The order in which this header was
80       * added is preserved.
81       *
82       * @param header the header to add
83       */
84      public void addHeader(final Header header) {
85          if (header == null) {
86              return;
87          }
88          headers.add(header);
89      }
90  
91      /**
92       * Removes the given header.
93       *
94       * @param header the header to remove
95       */
96      public void removeHeader(final Header header) {
97          if (header == null) {
98              return;
99          }
100         for (int i = 0; i < this.headers.size(); i++) {
101             final Header current = this.headers.get(i);
102             if (current == header || current.getName().equalsIgnoreCase(header.getName())
103                     && LangUtils.equals(header.getValue(), current.getValue())) {
104                 this.headers.remove(current);
105                 return;
106             }
107         }
108     }
109 
110     /**
111      * Replaces the first occurrence of the header with the same name. If no header with
112      * the same name is found the given header is added to the end of the list.
113      *
114      * @param header the new header that should replace the first header with the same
115      * name if present in the list.
116      *
117      * @since 5.0
118      */
119     public void setHeader(final Header header) {
120         if (header == null) {
121             return;
122         }
123         for (int i = 0; i < this.headers.size(); i++) {
124             final Header current = this.headers.get(i);
125             if (current.getName().equalsIgnoreCase(header.getName())) {
126                 this.headers.set(i, header);
127                 return;
128             }
129         }
130         this.headers.add(header);
131     }
132 
133     /**
134      * Sets all of the headers contained within this group overriding any
135      * existing headers. The headers are added in the order in which they appear
136      * in the array.
137      *
138      * @param headers the headers to set
139      */
140     public void setHeaders(final Header[] headers) {
141         clear();
142         if (headers == null) {
143             return;
144         }
145         Collections.addAll(this.headers, headers);
146     }
147 
148     /**
149      * Gets a header representing all of the header values with the given name.
150      * If more that one header with the given name exists the values will be
151      * combined with a "," as per RFC 2616.
152      *
153      * <p>Header name comparison is case insensitive.
154      *
155      * @param name the name of the header(s) to get
156      * @return a header with a condensed value or {@code null} if no
157      * headers by the given name are present
158      */
159     public Header getCondensedHeader(final String name) {
160         final Header[] hdrs = getHeaders(name);
161 
162         if (hdrs.length == 0) {
163             return null;
164         } else if (hdrs.length == 1) {
165             return hdrs[0];
166         } else {
167             final CharArrayBuffer valueBuffer = new CharArrayBuffer(128);
168             valueBuffer.append(hdrs[0].getValue());
169             for (int i = 1; i < hdrs.length; i++) {
170                 valueBuffer.append(", ");
171                 valueBuffer.append(hdrs[i].getValue());
172             }
173 
174             return new BasicHeader(name.toLowerCase(Locale.ROOT), valueBuffer.toString());
175         }
176     }
177 
178     /**
179      * Gets all of the headers with the given name.  The returned array
180      * maintains the relative order in which the headers were added.
181      *
182      * <p>Header name comparison is case insensitive.
183      *
184      * @param name the name of the header(s) to get
185      *
186      * @return an array of length &ge; 0
187      */
188     @Override
189     public Header[] getHeaders(final String name) {
190         List<Header> headersFound = null;
191         for (int i = 0; i < this.headers.size(); i++) {
192             final Header header = this.headers.get(i);
193             if (header.getName().equalsIgnoreCase(name)) {
194                 if (headersFound == null) {
195                     headersFound = new ArrayList<>();
196                 }
197                 headersFound.add(header);
198             }
199         }
200         return headersFound != null ? headersFound.toArray(new Header[headersFound.size()]) : EMPTY;
201     }
202 
203     /**
204      * Gets the first header with the given name.
205      *
206      * <p>Header name comparison is case insensitive.
207      *
208      * @param name the name of the header to get
209      * @return the first header or {@code null}
210      */
211     @Override
212     public Header getFirstHeader(final String name) {
213         for (int i = 0; i < this.headers.size(); i++) {
214             final Header header = this.headers.get(i);
215             if (header.getName().equalsIgnoreCase(name)) {
216                 return header;
217             }
218         }
219         return null;
220     }
221 
222     /**
223      * Gets single first header with the given name.
224      *
225      * <p>Header name comparison is case insensitive.
226      *
227      * @param name the name of the header to get
228      * @return the first header or {@code null}
229      * @throws ProtocolException in case multiple headers with the given name are found.
230      */
231     @Override
232     public Header getSingleHeader(final String name) throws ProtocolException {
233         int count = 0;
234         Header singleHeader = null;
235         for (int i = 0; i < this.headers.size(); i++) {
236             final Header header = this.headers.get(i);
237             if (header.getName().equalsIgnoreCase(name)) {
238                 singleHeader = header;
239                 count++;
240             }
241         }
242         if (count > 1) {
243             throw new ProtocolException("Multiple headers '" + name + "' found");
244         }
245         return singleHeader;
246     }
247 
248     /**
249      * Gets the last header with the given name.
250      *
251      * <p>Header name comparison is case insensitive.
252      *
253      * @param name the name of the header to get
254      * @return the last header or {@code null}
255      */
256     @Override
257     public Header getLastHeader(final String name) {
258         // start at the end of the list and work backwards
259         for (int i = headers.size() - 1; i >= 0; i--) {
260             final Header header = headers.get(i);
261             if (header.getName().equalsIgnoreCase(name)) {
262                 return header;
263             }
264         }
265 
266         return null;
267     }
268 
269     /**
270      * Gets all of the headers contained within this group.
271      *
272      * @return an array of length &ge; 0
273      */
274     @Override
275     public Header[] getAllHeaders() {
276         return headers.toArray(new Header[headers.size()]);
277     }
278 
279     /**
280      * Tests if headers with the given name are contained within this group.
281      *
282      * <p>Header name comparison is case insensitive.
283      *
284      * @param name the header name to test for
285      * @return {@code true} if at least one header with the name is
286      * contained, {@code false} otherwise
287      */
288     @Override
289     public boolean containsHeader(final String name) {
290         // HTTPCORE-361 : we don't use the for-each syntax, i.e.
291         //     for (Header header : headers)
292         // as that creates an Iterator that needs to be garbage-collected
293         for (int i = 0; i < this.headers.size(); i++) {
294             final Header header = this.headers.get(i);
295             if (header.getName().equalsIgnoreCase(name)) {
296                 return true;
297             }
298         }
299 
300         return false;
301     }
302 
303     /**
304      * Checks if a certain header is present in this message and how many times.
305      * <p>Header name comparison is case insensitive.
306      *
307      * @param name the header name to check for.
308      * @return number of occurrences of the header in the message.
309      */
310     @Override
311     public int containsHeaders(final String name) {
312         // HTTPCORE-361 : we don't use the for-each syntax, i.e.
313         //     for (Header header : headers)
314         // as that creates an Iterator that needs to be garbage-collected
315         int count = 0;
316         for (int i = 0; i < this.headers.size(); i++) {
317             final Header header = this.headers.get(i);
318             if (header.getName().equalsIgnoreCase(name)) {
319                 count++;
320             }
321         }
322         return count;
323     }
324 
325     /**
326      * Returns an iterator over this group of headers.
327      *
328      * @return iterator over this group of headers.
329      *
330      * @since 5.0
331      */
332     @Override
333     public Iterator<Header> headerIterator() {
334         return new BasicListHeaderIterator(this.headers, null);
335     }
336 
337     /**
338      * Returns an iterator over the headers with a given name in this group.
339      *
340      * @param name      the name of the headers over which to iterate, or
341      *                  {@code null} for all headers
342      *
343      * @return iterator over some headers in this group.
344      *
345      * @since 5.0
346      */
347     @Override
348     public Iterator<Header> headerIterator(final String name) {
349         return new BasicListHeaderIterator(this.headers, name);
350     }
351 
352     /**
353      * Removes all headers with a given name in this group.
354      *
355      * @param name      the name of the headers to be removed.
356      *
357      * @since 5.0
358      */
359     public void removeHeaders(final String name) {
360         if (name == null) {
361             return;
362         }
363         for (final Iterator<Header> i = headerIterator(); i.hasNext(); ) {
364             final Header header = i.next();
365             if (header.getName().equalsIgnoreCase(name)) {
366                 i.remove();
367             }
368         }
369     }
370 
371     @Override
372     public String toString() {
373         return this.headers.toString();
374     }
375 
376 }