--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>A container for name/value pairs, known as fields.</p>
+ * <p>A {@link Field} is composed of a name string that can be case-sensitive
+ * or case-insensitive (by specifying the option at the constructor) and
+ * of a case-sensitive set of value strings.</p>
+ * <p>The implementation of this class is not thread safe.</p>
+ */
+public class Fields implements Iterable<Fields.Field>
+{
+ private final boolean caseSensitive;
+ private final Map<String, Field> fields;
+
+ /**
+ * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+ * @see #Fields(Fields, boolean)
+ */
+ public Fields()
+ {
+ this(false);
+ }
+
+ /**
+ * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+ * @param caseSensitive whether this {@link Fields} instance must be case sensitive
+ * @see #Fields(Fields, boolean)
+ */
+ public Fields(boolean caseSensitive)
+ {
+ this.caseSensitive = caseSensitive;
+ fields = new LinkedHashMap<>();
+ }
+
+ /**
+ * <p>Creates a {@link Fields} instance by copying the fields from the given
+ * {@link Fields} and making it (im)mutable depending on the given {@code immutable} parameter</p>
+ *
+ * @param original the {@link Fields} to copy fields from
+ * @param immutable whether this instance is immutable
+ */
+ public Fields(Fields original, boolean immutable)
+ {
+ this.caseSensitive = original.caseSensitive;
+ Map<String, Field> copy = new LinkedHashMap<>();
+ copy.putAll(original.fields);
+ fields = immutable ? Collections.unmodifiableMap(copy) : copy;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null || getClass() != obj.getClass())
+ return false;
+ Fields that = (Fields)obj;
+ if (getSize() != that.getSize())
+ return false;
+ if (caseSensitive != that.caseSensitive)
+ return false;
+ for (Map.Entry<String, Field> entry : fields.entrySet())
+ {
+ String name = entry.getKey();
+ Field value = entry.getValue();
+ if (!value.equals(that.get(name), caseSensitive))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return fields.hashCode();
+ }
+
+ /**
+ * @return a set of field names
+ */
+ public Set<String> getNames()
+ {
+ Set<String> result = new LinkedHashSet<>();
+ for (Field field : fields.values())
+ result.add(field.getName());
+ return result;
+ }
+
+ private String normalizeName(String name)
+ {
+ return caseSensitive ? name : name.toLowerCase(Locale.ENGLISH);
+ }
+
+ /**
+ * @param name the field name
+ * @return the {@link Field} with the given name, or null if no such field exists
+ */
+ public Field get(String name)
+ {
+ return fields.get(normalizeName(name));
+ }
+
+ /**
+ * <p>Inserts or replaces the given name/value pair as a single-valued {@link Field}.</p>
+ *
+ * @param name the field name
+ * @param value the field value
+ */
+ public void put(String name, String value)
+ {
+ // Preserve the case for the field name
+ Field field = new Field(name, value);
+ fields.put(normalizeName(name), field);
+ }
+
+ /**
+ * <p>Inserts or replaces the given {@link Field}, mapped to the {@link Field#getName() field's name}</p>
+ *
+ * @param field the field to put
+ */
+ public void put(Field field)
+ {
+ if (field != null)
+ fields.put(normalizeName(field.getName()), field);
+ }
+
+ /**
+ * <p>Adds the given value to a field with the given name,
+ * creating a {@link Field} is none exists for the given name.</p>
+ *
+ * @param name the field name
+ * @param value the field value to add
+ */
+ public void add(String name, String value)
+ {
+ String key = normalizeName(name);
+ Field field = fields.get(key);
+ if (field == null)
+ {
+ // Preserve the case for the field name
+ field = new Field(name, value);
+ fields.put(key, field);
+ }
+ else
+ {
+ field = new Field(field.getName(), field.getValues(), value);
+ fields.put(key, field);
+ }
+ }
+
+ /**
+ * <p>Removes the {@link Field} with the given name</p>
+ *
+ * @param name the name of the field to remove
+ * @return the removed field, or null if no such field existed
+ */
+ public Field remove(String name)
+ {
+ return fields.remove(normalizeName(name));
+ }
+
+ /**
+ * <p>Empties this {@link Fields} instance from all fields</p>
+ * @see #isEmpty()
+ */
+ public void clear()
+ {
+ fields.clear();
+ }
+
+ /**
+ * @return whether this {@link Fields} instance is empty
+ */
+ public boolean isEmpty()
+ {
+ return fields.isEmpty();
+ }
+
+ /**
+ * @return the number of fields
+ */
+ public int getSize()
+ {
+ return fields.size();
+ }
+
+ /**
+ * @return an iterator over the {@link Field}s present in this instance
+ */
+ @Override
+ public Iterator<Field> iterator()
+ {
+ return fields.values().iterator();
+ }
+
+ @Override
+ public String toString()
+ {
+ return fields.toString();
+ }
+
+ /**
+ * <p>A named list of string values.</p>
+ * <p>The name is case-sensitive and there must be at least one value.</p>
+ */
+ public static class Field
+ {
+ private final String name;
+ private final List<String> values;
+
+ public Field(String name, String value)
+ {
+ this(name, Collections.singletonList(value));
+ }
+
+ private Field(String name, List<String> values, String... moreValues)
+ {
+ this.name = name;
+ List<String> list = new ArrayList<>(values.size() + moreValues.length);
+ list.addAll(values);
+ list.addAll(Arrays.asList(moreValues));
+ this.values = Collections.unmodifiableList(list);
+ }
+
+ public boolean equals(Field that, boolean caseSensitive)
+ {
+ if (this == that)
+ return true;
+ if (that == null)
+ return false;
+ if (caseSensitive)
+ return equals(that);
+ return name.equalsIgnoreCase(that.name) && values.equals(that.values);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null || getClass() != obj.getClass())
+ return false;
+ Field that = (Field)obj;
+ return name.equals(that.name) && values.equals(that.values);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = name.hashCode();
+ result = 31 * result + values.hashCode();
+ return result;
+ }
+
+ /**
+ * @return the field's name
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * @return the first field's value
+ */
+ public String getValue()
+ {
+ return values.get(0);
+ }
+
+ /**
+ * <p>Attempts to convert the result of {@link #getValue()} to an integer,
+ * returning it if the conversion is successful; returns null if the
+ * result of {@link #getValue()} is null.</p>
+ *
+ * @return the result of {@link #getValue()} converted to an integer, or null
+ * @throws NumberFormatException if the conversion fails
+ */
+ public Integer getValueAsInt()
+ {
+ final String value = getValue();
+ return value == null ? null : Integer.valueOf(value);
+ }
+
+ /**
+ * @return the field's values
+ */
+ public List<String> getValues()
+ {
+ return values;
+ }
+
+ /**
+ * @return whether the field has multiple values
+ */
+ public boolean hasMultipleValues()
+ {
+ return values.size() > 1;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s=%s", name, values);
+ }
+ }
+}