1 //
2 // ========================================================================
3 // Copyright (c) 1995-2016 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.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.LinkedHashMap;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.Set;
31
32 /**
33 * <p>A container for name/value pairs, known as fields.</p>
34 * <p>A {@link Field} is composed of a name string that can be case-sensitive
35 * or case-insensitive (by specifying the option at the constructor) and
36 * of a case-sensitive set of value strings.</p>
37 * <p>The implementation of this class is not thread safe.</p>
38 */
39 public class Fields implements Iterable<Fields.Field>
40 {
41 private final boolean caseSensitive;
42 private final Map<String, Field> fields;
43
44 /**
45 * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
46 * @see #Fields(Fields, boolean)
47 */
48 public Fields()
49 {
50 this(false);
51 }
52
53 /**
54 * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
55 * @param caseSensitive whether this {@link Fields} instance must be case sensitive
56 * @see #Fields(Fields, boolean)
57 */
58 public Fields(boolean caseSensitive)
59 {
60 this.caseSensitive = caseSensitive;
61 fields = new LinkedHashMap<>();
62 }
63
64 /**
65 * <p>Creates a {@link Fields} instance by copying the fields from the given
66 * {@link Fields} and making it (im)mutable depending on the given {@code immutable} parameter</p>
67 *
68 * @param original the {@link Fields} to copy fields from
69 * @param immutable whether this instance is immutable
70 */
71 public Fields(Fields original, boolean immutable)
72 {
73 this.caseSensitive = original.caseSensitive;
74 Map<String, Field> copy = new LinkedHashMap<>();
75 copy.putAll(original.fields);
76 fields = immutable ? Collections.unmodifiableMap(copy) : copy;
77 }
78
79 @Override
80 public boolean equals(Object obj)
81 {
82 if (this == obj)
83 return true;
84 if (obj == null || getClass() != obj.getClass())
85 return false;
86 Fields that = (Fields)obj;
87 if (getSize() != that.getSize())
88 return false;
89 if (caseSensitive != that.caseSensitive)
90 return false;
91 for (Map.Entry<String, Field> entry : fields.entrySet())
92 {
93 String name = entry.getKey();
94 Field value = entry.getValue();
95 if (!value.equals(that.get(name), caseSensitive))
96 return false;
97 }
98 return true;
99 }
100
101 @Override
102 public int hashCode()
103 {
104 return fields.hashCode();
105 }
106
107 /**
108 * @return a set of field names
109 */
110 public Set<String> getNames()
111 {
112 Set<String> result = new LinkedHashSet<>();
113 for (Field field : fields.values())
114 result.add(field.getName());
115 return result;
116 }
117
118 private String normalizeName(String name)
119 {
120 return caseSensitive ? name : name.toLowerCase(Locale.ENGLISH);
121 }
122
123 /**
124 * @param name the field name
125 * @return the {@link Field} with the given name, or null if no such field exists
126 */
127 public Field get(String name)
128 {
129 return fields.get(normalizeName(name));
130 }
131
132 /**
133 * <p>Inserts or replaces the given name/value pair as a single-valued {@link Field}.</p>
134 *
135 * @param name the field name
136 * @param value the field value
137 */
138 public void put(String name, String value)
139 {
140 // Preserve the case for the field name
141 Field field = new Field(name, value);
142 fields.put(normalizeName(name), field);
143 }
144
145 /**
146 * <p>Inserts or replaces the given {@link Field}, mapped to the {@link Field#getName() field's name}</p>
147 *
148 * @param field the field to put
149 */
150 public void put(Field field)
151 {
152 if (field != null)
153 fields.put(normalizeName(field.getName()), field);
154 }
155
156 /**
157 * <p>Adds the given value to a field with the given name,
158 * creating a {@link Field} is none exists for the given name.</p>
159 *
160 * @param name the field name
161 * @param value the field value to add
162 */
163 public void add(String name, String value)
164 {
165 String key = normalizeName(name);
166 Field field = fields.get(key);
167 if (field == null)
168 {
169 // Preserve the case for the field name
170 field = new Field(name, value);
171 fields.put(key, field);
172 }
173 else
174 {
175 field = new Field(field.getName(), field.getValues(), value);
176 fields.put(key, field);
177 }
178 }
179
180 /**
181 * <p>Removes the {@link Field} with the given name</p>
182 *
183 * @param name the name of the field to remove
184 * @return the removed field, or null if no such field existed
185 */
186 public Field remove(String name)
187 {
188 return fields.remove(normalizeName(name));
189 }
190
191 /**
192 * <p>Empties this {@link Fields} instance from all fields</p>
193 * @see #isEmpty()
194 */
195 public void clear()
196 {
197 fields.clear();
198 }
199
200 /**
201 * @return whether this {@link Fields} instance is empty
202 */
203 public boolean isEmpty()
204 {
205 return fields.isEmpty();
206 }
207
208 /**
209 * @return the number of fields
210 */
211 public int getSize()
212 {
213 return fields.size();
214 }
215
216 /**
217 * @return an iterator over the {@link Field}s present in this instance
218 */
219 @Override
220 public Iterator<Field> iterator()
221 {
222 return fields.values().iterator();
223 }
224
225 @Override
226 public String toString()
227 {
228 return fields.toString();
229 }
230
231 /**
232 * <p>A named list of string values.</p>
233 * <p>The name is case-sensitive and there must be at least one value.</p>
234 */
235 public static class Field
236 {
237 private final String name;
238 private final List<String> values;
239
240 public Field(String name, String value)
241 {
242 this(name, Collections.singletonList(value));
243 }
244
245 private Field(String name, List<String> values, String... moreValues)
246 {
247 this.name = name;
248 List<String> list = new ArrayList<>(values.size() + moreValues.length);
249 list.addAll(values);
250 list.addAll(Arrays.asList(moreValues));
251 this.values = Collections.unmodifiableList(list);
252 }
253
254 public boolean equals(Field that, boolean caseSensitive)
255 {
256 if (this == that)
257 return true;
258 if (that == null)
259 return false;
260 if (caseSensitive)
261 return equals(that);
262 return name.equalsIgnoreCase(that.name) && values.equals(that.values);
263 }
264
265 @Override
266 public boolean equals(Object obj)
267 {
268 if (this == obj)
269 return true;
270 if (obj == null || getClass() != obj.getClass())
271 return false;
272 Field that = (Field)obj;
273 return name.equals(that.name) && values.equals(that.values);
274 }
275
276 @Override
277 public int hashCode()
278 {
279 int result = name.hashCode();
280 result = 31 * result + values.hashCode();
281 return result;
282 }
283
284 /**
285 * @return the field's name
286 */
287 public String getName()
288 {
289 return name;
290 }
291
292 /**
293 * @return the first field's value
294 */
295 public String getValue()
296 {
297 return values.get(0);
298 }
299
300 /**
301 * <p>Attempts to convert the result of {@link #getValue()} to an integer,
302 * returning it if the conversion is successful; returns null if the
303 * result of {@link #getValue()} is null.</p>
304 *
305 * @return the result of {@link #getValue()} converted to an integer, or null
306 * @throws NumberFormatException if the conversion fails
307 */
308 public Integer getValueAsInt()
309 {
310 final String value = getValue();
311 return value == null ? null : Integer.valueOf(value);
312 }
313
314 /**
315 * @return the field's values
316 */
317 public List<String> getValues()
318 {
319 return values;
320 }
321
322 /**
323 * @return whether the field has multiple values
324 */
325 public boolean hasMultipleValues()
326 {
327 return values.size() > 1;
328 }
329
330 @Override
331 public String toString()
332 {
333 return String.format("%s=%s", name, values);
334 }
335 }
336 }