View Javadoc

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