View Javadoc

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 }