1 //
2 // ========================================================================
3 // Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.util;
20
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.Iterator;
24 import java.util.LinkedHashMap;
25 import java.util.LinkedHashSet;
26 import java.util.Map;
27 import java.util.Set;
28
29 /**
30 * <p>A container for name/value pairs, known as fields.</p>
31 * <p>A {@link Field} is composed of a case-insensitive name string and
32 * of a case-sensitive set of value strings.</p>
33 * <p>The implementation of this class is not thread safe.</p>
34 */
35 public class Fields implements Iterable<Fields.Field>
36 {
37 private final Map<String, Field> fields;
38
39 /**
40 * <p>Creates an empty modifiable {@link Fields} instance.</p>
41 * @see #Fields(Fields, boolean)
42 */
43 public Fields()
44 {
45 fields = new LinkedHashMap<>();
46 }
47
48 /**
49 * <p>Creates a {@link Fields} instance by copying the fields from the given
50 * {@link Fields} and making it (im)mutable depending on the given {@code immutable} parameter</p>
51 *
52 * @param original the {@link Fields} to copy fields from
53 * @param immutable whether this instance is immutable
54 */
55 public Fields(Fields original, boolean immutable)
56 {
57 Map<String, Field> copy = new LinkedHashMap<>();
58 copy.putAll(original.fields);
59 fields = immutable ? Collections.unmodifiableMap(copy) : copy;
60 }
61
62 @Override
63 public boolean equals(Object obj)
64 {
65 if (this == obj)
66 return true;
67 if (obj == null || getClass() != obj.getClass())
68 return false;
69 Fields that = (Fields)obj;
70 return fields.equals(that.fields);
71 }
72
73 @Override
74 public int hashCode()
75 {
76 return fields.hashCode();
77 }
78
79 /**
80 * @return a set of field names
81 */
82 public Set<String> names()
83 {
84 Set<String> result = new LinkedHashSet<>();
85 for (Field field : fields.values())
86 result.add(field.name());
87 return result;
88 }
89
90 /**
91 * @param name the field name
92 * @return the {@link Field} with the given name, or null if no such field exists
93 */
94 public Field get(String name)
95 {
96 return fields.get(name.trim().toLowerCase());
97 }
98
99 /**
100 * <p>Inserts or replaces the given name/value pair as a single-valued {@link Field}.</p>
101 *
102 * @param name the field name
103 * @param value the field value
104 */
105 public void put(String name, String value)
106 {
107 name = name.trim();
108 // Preserve the case for the field name
109 Field field = new Field(name, value);
110 fields.put(name.toLowerCase(), field);
111 }
112
113 /**
114 * <p>Inserts or replaces the given {@link Field}, mapped to the {@link Field#name() field's name}</p>
115 *
116 * @param field the field to put
117 */
118 public void put(Field field)
119 {
120 if (field != null)
121 fields.put(field.name().toLowerCase(), field);
122 }
123
124 /**
125 * <p>Adds the given value to a field with the given name,
126 * creating a {@link Field} is none exists for the given name.</p>
127 *
128 * @param name the field name
129 * @param value the field value to add
130 */
131 public void add(String name, String value)
132 {
133 name = name.trim();
134 Field field = fields.get(name.toLowerCase());
135 if (field == null)
136 {
137 field = new Field(name, value);
138 fields.put(name.toLowerCase(), field);
139 }
140 else
141 {
142 field = new Field(field.name(), field.values(), value);
143 fields.put(name.toLowerCase(), field);
144 }
145 }
146
147 /**
148 * <p>Removes the {@link Field} with the given name</p>
149 *
150 * @param name the name of the field to remove
151 * @return the removed field, or null if no such field existed
152 */
153 public Field remove(String name)
154 {
155 name = name.trim();
156 return fields.remove(name.toLowerCase());
157 }
158
159 /**
160 * <p>Empties this {@link Fields} instance from all fields</p>
161 * @see #isEmpty()
162 */
163 public void clear()
164 {
165 fields.clear();
166 }
167
168 /**
169 * @return whether this {@link Fields} instance is empty
170 */
171 public boolean isEmpty()
172 {
173 return fields.isEmpty();
174 }
175
176 /**
177 * @return the number of fields
178 */
179 public int size()
180 {
181 return fields.size();
182 }
183
184 /**
185 * @return an iterator over the {@link Field}s present in this instance
186 */
187 @Override
188 public Iterator<Field> iterator()
189 {
190 return fields.values().iterator();
191 }
192
193 @Override
194 public String toString()
195 {
196 return fields.toString();
197 }
198
199 /**
200 * <p>A named list of string values.</p>
201 * <p>The name is case-sensitive and there must be at least one value.</p>
202 */
203 public static class Field
204 {
205 private final String name;
206 private final String[] values;
207
208 private Field(String name, String value)
209 {
210 this(name, new String[]{value});
211 }
212
213 private Field(String name, String[] values, String... moreValues)
214 {
215 this.name = name;
216 this.values = new String[values.length + moreValues.length];
217 System.arraycopy(values, 0, this.values, 0, values.length);
218 System.arraycopy(moreValues, 0, this.values, values.length, moreValues.length);
219 }
220
221 @Override
222 public boolean equals(Object obj)
223 {
224 if (this == obj)
225 return true;
226 if (obj == null || getClass() != obj.getClass())
227 return false;
228 Field that = (Field)obj;
229 // Field names must be lowercase, thus we lowercase them before transmission, but keep them as is
230 // internally. That's why we've to compare them case insensitive.
231 return name.equalsIgnoreCase(that.name) && Arrays.equals(values, that.values);
232 }
233
234 @Override
235 public int hashCode()
236 {
237 int result = name.toLowerCase().hashCode();
238 result = 31 * result + Arrays.hashCode(values);
239 return result;
240 }
241
242 /**
243 * @return the field's name
244 */
245 public String name()
246 {
247 return name;
248 }
249
250 /**
251 * @return the first field's value
252 */
253 public String value()
254 {
255 return values[0];
256 }
257
258 /**
259 * <p>Attempts to convert the result of {@link #value()} to an integer,
260 * returning it if the conversion is successful; returns null if the
261 * result of {@link #value()} is null.</p>
262 *
263 * @return the result of {@link #value()} converted to an integer, or null
264 * @throws NumberFormatException if the conversion fails
265 */
266 public Integer valueAsInt()
267 {
268 final String value = value();
269 return value == null ? null : Integer.valueOf(value);
270 }
271
272 /**
273 * @return the field's values
274 */
275 public String[] values()
276 {
277 return values;
278 }
279
280 /**
281 * @return whether the field has multiple values
282 */
283 public boolean hasMultipleValues()
284 {
285 return values.length > 1;
286 }
287
288 @Override
289 public String toString()
290 {
291 return Arrays.toString(values);
292 }
293 }
294 }