//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
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;
A container for name/value pairs, known as fields.
A 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.
The implementation of this class is not thread safe.
/**
* <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;
Creates an empty, modifiable, case insensitive Fields
instance.
See Also:
/**
* <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
*
* @see #Fields(Fields, boolean)
*/
public Fields()
{
this(false);
}
Creates an empty, modifiable, case insensitive Fields
instance.
Params: - caseSensitive – whether this
Fields
instance must be case sensitive
See Also:
/**
* <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<>();
}
Creates a Fields
instance by copying the fields from the given Fields
and making it (im)mutable depending on the given immutable
parameter
Params: - original – the
Fields
to copy fields from - immutable – whether this instance is immutable
/**
* <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();
}
Returns: a set of field names
/**
* @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);
}
Params: - name – the field name
Returns: the Field
with the given name, or null if no such field exists
/**
* @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));
}
Inserts or replaces the given name/value pair as a single-valued Field
.
Params: - name – the field name
- value – the field value
/**
* <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);
}
Inserts or replaces the given Field
, mapped to the field's name
Params: - field – the field to put
/**
* <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);
}
Adds the given value to a field with the given name, creating a Field
is none exists for the given name.
Params: - name – the field name
- value – the field value to add
/**
* <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);
}
}
Removes the Field
with the given name
Params: - name – the name of the field to remove
Returns: the removed field, or null if no such field existed
/**
* <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));
}
Empties this Fields
instance from all fields
See Also:
/**
* <p>Empties this {@link Fields} instance from all fields</p>
*
* @see #isEmpty()
*/
public void clear()
{
fields.clear();
}
Returns: whether this Fields
instance is empty
/**
* @return whether this {@link Fields} instance is empty
*/
public boolean isEmpty()
{
return fields.isEmpty();
}
Returns: the number of fields
/**
* @return the number of fields
*/
public int getSize()
{
return fields.size();
}
Returns: an iterator over the Field
s present in this instance
/**
* @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();
}
A named list of string values.
The name is case-sensitive and there must be at least one value.
/**
* <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);
}
@SuppressWarnings("ReferenceEquality")
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;
}
Returns: the field's name
/**
* @return the field's name
*/
public String getName()
{
return name;
}
Returns: the first field's value
/**
* @return the first field's value
*/
public String getValue()
{
return values.get(0);
}
Attempts to convert the result of getValue()
to an integer, returning it if the conversion is successful; returns null if the result of getValue()
is null.
Throws: - NumberFormatException – if the conversion fails
Returns: the result of getValue()
converted to an integer, or null
/**
* <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)Integer.parseInt(value);
}
Returns: the field's values
/**
* @return the field's values
*/
public List<String> getValues()
{
return values;
}
Returns: whether the field has multiple 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);
}
}
}