// // ======================================================================== // Copyright (c) 1995-2017 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); } } }