001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.message; 029 030import java.io.Serializable; 031import java.util.ArrayList; 032import java.util.Collections; 033import java.util.List; 034import java.util.Locale; 035 036import org.apache.http.Header; 037import org.apache.http.HeaderIterator; 038import org.apache.http.util.CharArrayBuffer; 039 040/** 041 * A class for combining a set of headers. 042 * This class allows for multiple headers with the same name and 043 * keeps track of the order in which headers were added. 044 * 045 * 046 * @since 4.0 047 */ 048public class HeaderGroup implements Cloneable, Serializable { 049 050 private static final long serialVersionUID = 2608834160639271617L; 051 052 private final Header[] EMPTY = new Header[] {}; 053 054 /** The list of headers for this group, in the order in which they were added */ 055 private final List<Header> headers; 056 057 /** 058 * Constructor for HeaderGroup. 059 */ 060 public HeaderGroup() { 061 this.headers = new ArrayList<Header>(16); 062 } 063 064 /** 065 * Removes any contained headers. 066 */ 067 public void clear() { 068 headers.clear(); 069 } 070 071 /** 072 * Adds the given header to the group. The order in which this header was 073 * added is preserved. 074 * 075 * @param header the header to add 076 */ 077 public void addHeader(final Header header) { 078 if (header == null) { 079 return; 080 } 081 headers.add(header); 082 } 083 084 /** 085 * Removes the given header. 086 * 087 * @param header the header to remove 088 */ 089 public void removeHeader(final Header header) { 090 if (header == null) { 091 return; 092 } 093 headers.remove(header); 094 } 095 096 /** 097 * Replaces the first occurence of the header with the same name. If no header with 098 * the same name is found the given header is added to the end of the list. 099 * 100 * @param header the new header that should replace the first header with the same 101 * name if present in the list. 102 */ 103 public void updateHeader(final Header header) { 104 if (header == null) { 105 return; 106 } 107 // HTTPCORE-361 : we don't use the for-each syntax, i.e. 108 // for (Header header : headers) 109 // as that creates an Iterator that needs to be garbage-collected 110 for (int i = 0; i < this.headers.size(); i++) { 111 final Header current = this.headers.get(i); 112 if (current.getName().equalsIgnoreCase(header.getName())) { 113 this.headers.set(i, header); 114 return; 115 } 116 } 117 this.headers.add(header); 118 } 119 120 /** 121 * Sets all of the headers contained within this group overriding any 122 * existing headers. The headers are added in the order in which they appear 123 * in the array. 124 * 125 * @param headers the headers to set 126 */ 127 public void setHeaders(final Header[] headers) { 128 clear(); 129 if (headers == null) { 130 return; 131 } 132 Collections.addAll(this.headers, headers); 133 } 134 135 /** 136 * Gets a header representing all of the header values with the given name. 137 * If more that one header with the given name exists the values will be 138 * combined with a "," as per RFC 2616. 139 * 140 * <p>Header name comparison is case insensitive. 141 * 142 * @param name the name of the header(s) to get 143 * @return a header with a condensed value or {@code null} if no 144 * headers by the given name are present 145 */ 146 public Header getCondensedHeader(final String name) { 147 final Header[] hdrs = getHeaders(name); 148 149 if (hdrs.length == 0) { 150 return null; 151 } else if (hdrs.length == 1) { 152 return hdrs[0]; 153 } else { 154 final CharArrayBuffer valueBuffer = new CharArrayBuffer(128); 155 valueBuffer.append(hdrs[0].getValue()); 156 for (int i = 1; i < hdrs.length; i++) { 157 valueBuffer.append(", "); 158 valueBuffer.append(hdrs[i].getValue()); 159 } 160 161 return new BasicHeader(name.toLowerCase(Locale.ROOT), valueBuffer.toString()); 162 } 163 } 164 165 /** 166 * Gets all of the headers with the given name. The returned array 167 * maintains the relative order in which the headers were added. 168 * 169 * <p>Header name comparison is case insensitive. 170 * 171 * @param name the name of the header(s) to get 172 * 173 * @return an array of length ≥ 0 174 */ 175 public Header[] getHeaders(final String name) { 176 List<Header> headersFound = null; 177 // HTTPCORE-361 : we don't use the for-each syntax, i.e. 178 // for (Header header : headers) 179 // as that creates an Iterator that needs to be garbage-collected 180 for (int i = 0; i < this.headers.size(); i++) { 181 final Header header = this.headers.get(i); 182 if (header.getName().equalsIgnoreCase(name)) { 183 if (headersFound == null) { 184 headersFound = new ArrayList<Header>(); 185 } 186 headersFound.add(header); 187 } 188 } 189 return headersFound != null ? headersFound.toArray(new Header[headersFound.size()]) : EMPTY; 190 } 191 192 /** 193 * Gets the first header with the given name. 194 * 195 * <p>Header name comparison is case insensitive. 196 * 197 * @param name the name of the header to get 198 * @return the first header or {@code null} 199 */ 200 public Header getFirstHeader(final String name) { 201 // HTTPCORE-361 : we don't use the for-each syntax, i.e. 202 // for (Header header : headers) 203 // as that creates an Iterator that needs to be garbage-collected 204 for (int i = 0; i < this.headers.size(); i++) { 205 final Header header = this.headers.get(i); 206 if (header.getName().equalsIgnoreCase(name)) { 207 return header; 208 } 209 } 210 return null; 211 } 212 213 /** 214 * Gets the last header with the given name. 215 * 216 * <p>Header name comparison is case insensitive. 217 * 218 * @param name the name of the header to get 219 * @return the last header or {@code null} 220 */ 221 public Header getLastHeader(final String name) { 222 // start at the end of the list and work backwards 223 for (int i = headers.size() - 1; i >= 0; i--) { 224 final Header header = headers.get(i); 225 if (header.getName().equalsIgnoreCase(name)) { 226 return header; 227 } 228 } 229 230 return null; 231 } 232 233 /** 234 * Gets all of the headers contained within this group. 235 * 236 * @return an array of length ≥ 0 237 */ 238 public Header[] getAllHeaders() { 239 return headers.toArray(new Header[headers.size()]); 240 } 241 242 /** 243 * Tests if headers with the given name are contained within this group. 244 * 245 * <p>Header name comparison is case insensitive. 246 * 247 * @param name the header name to test for 248 * @return {@code true} if at least one header with the name is 249 * contained, {@code false} otherwise 250 */ 251 public boolean containsHeader(final String name) { 252 // HTTPCORE-361 : we don't use the for-each syntax, i.e. 253 // for (Header header : headers) 254 // as that creates an Iterator that needs to be garbage-collected 255 for (int i = 0; i < this.headers.size(); i++) { 256 final Header header = this.headers.get(i); 257 if (header.getName().equalsIgnoreCase(name)) { 258 return true; 259 } 260 } 261 262 return false; 263 } 264 265 /** 266 * Returns an iterator over this group of headers. 267 * 268 * @return iterator over this group of headers. 269 * 270 * @since 4.0 271 */ 272 public HeaderIterator iterator() { 273 return new BasicListHeaderIterator(this.headers, null); 274 } 275 276 /** 277 * Returns an iterator over the headers with a given name in this group. 278 * 279 * @param name the name of the headers over which to iterate, or 280 * {@code null} for all headers 281 * 282 * @return iterator over some headers in this group. 283 * 284 * @since 4.0 285 */ 286 public HeaderIterator iterator(final String name) { 287 return new BasicListHeaderIterator(this.headers, name); 288 } 289 290 /** 291 * Returns a copy of this object 292 * 293 * @return copy of this object 294 * 295 * @since 4.0 296 */ 297 public HeaderGroup copy() { 298 final HeaderGroup clone = new HeaderGroup(); 299 clone.headers.addAll(this.headers); 300 return clone; 301 } 302 303 @Override 304 public Object clone() throws CloneNotSupportedException { 305 return super.clone(); 306 } 307 308 @Override 309 public String toString() { 310 return this.headers.toString(); 311 } 312 313}