/*
* Copyright (c) 2012 - 2016 Jadler contributors
* This program is made available under the terms of the MIT License.
*/
package net.jadler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;
import org.apache.commons.lang.Validate;
/**
* <p>This class represents a key-value data structure with following features:</p>
*
* <ul>
* <li>multiple values for one key</li>
* <li>key case insensitivity</li>
* <li>immutability</li>
* </ul>
*
* <p>This structure is used in Jadler for modeling request/response headers and request parameters.</p>
*
* <p>Please note this class is immutable and therefore thread safe. All addition operations
* ({@link #add(java.lang.String, java.lang.String)}, {@link #addAll(net.jadler.KeyValues)} create new instances
* rather than modifying the instance.</p>
*
* @see Request
* @see net.jadler.stubbing.StubResponse
*/
public class KeyValues {
private final MultiMap values;
/**
* An empty instance.
*/
public static final KeyValues EMPTY = new KeyValues();
/**
* Creates new empty instance.
*/
public KeyValues() {
this.values = new MultiValueMap();
}
/**
* Adds new key-value pair. Supports multi-values for one key (if there has already been added
* some value with this key, additional value is added instead of rewriting). Please note this method
* creates new instance containing all existing values plus the new one rather than modifying this instance.
* @param key key (cannot be empty)
* @param value value (cannot be {@code null}, however can be empty for valueless headers)
* @return an exact copy of this instance containing all existing values plus the new one
*/
@SuppressWarnings("unchecked")
public KeyValues add(final String key, final String value) {
Validate.notEmpty(key, "key cannot be empty");
Validate.notNull(value, "value cannot be null, use an empty string instead");
final KeyValues res = new KeyValues();
res.values.putAll(this.values);
res.values.put(key.toLowerCase(), value);
return res;
}
/**
* Adds all values from the given instance. Supports multi-values for one key (if there has already been added
* some value with this key, additional value is added instead of rewriting). Please note this method
* creates new instance containing all existing values plus the new ones rather than modifying this instance.
* @param keyValues values to be added no(cannot be {@code null})
* @return an exact copy of this instance containing all existing values plus the new ones
*/
@SuppressWarnings("unchecked")
public KeyValues addAll(final KeyValues keyValues) {
Validate.notNull(keyValues, "keyValues cannot be null");
final KeyValues res = new KeyValues();
res.values.putAll(this.values);
res.values.putAll(keyValues.values);
return res;
}
/**
* Returns the first value for the given key
* @param key key (case insensitive)
* @return single (first) value for the given key or {@code null}, if there is no such a key in this instance
*/
public String getValue(final String key) {
Validate.notEmpty(key, "key cannot be empty");
final List<String> allValues = this.getValues(key);
return allValues != null ? allValues.get(0) : null;
}
/**
* Returns all values for the given key
* @param key key (case insensitive)
* @return all values of the given header or {@code null}, if there is no such a key in this instance
*/
public List<String> getValues(final String key) {
Validate.notEmpty(key, "name cannot be empty");
@SuppressWarnings("unchecked")
final List<String> result = (List<String>) values.get(key.toLowerCase());
return result == null || result.isEmpty() ? null : new ArrayList<String>(result);
}
/**
* @return all keys (lower-cased) from this instance (never returns {@code null})
*/
public Set<String> getKeys() {
@SuppressWarnings("unchecked")
final Set<String> result = new HashSet<String>(this.values.keySet());
return result;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (@SuppressWarnings("unchecked")final Iterator<Map.Entry<String, Collection<String>>> it
= this.values.entrySet().iterator(); it.hasNext();) {
final Map.Entry<String, Collection<String>> e = it.next();
for (final Iterator<String> it2 = e.getValue().iterator(); it2.hasNext();) {
sb.append(e.getKey()).append(": ").append(it2.next());
if (it2.hasNext()) {
sb.append(", ");
}
}
if (it.hasNext()) {
sb.append(", ");
}
}
return sb.toString();
}
@Override
public int hashCode() {
int hash = 5;
hash = 43 * hash + (this.values != null ? this.values.hashCode() : 0);
return hash;
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final KeyValues other = (KeyValues) obj;
if (this.values != other.values && (this.values == null || !this.values.equals(other.values))) {
return false;
}
return true;
}
}