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