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