jodd.http.HttpMultiMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jodd-all Show documentation
Show all versions of jodd-all Show documentation
Jodd bundle - all classes in one jar
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package jodd.http;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
/**
* General purpose HTTP multi-map. It's optimized Linked-HashMap, designed for
* small number of items and String
non-null keys. It stores keys
* in case-sensitive way, but, by default, you can read them in case-insensitive
* way.
*/
public class HttpMultiMap implements Iterable> {
private static final int BUCKET_SIZE = 31;
private final boolean caseSensitive;
@SuppressWarnings("unchecked")
private final MapEntry[] entries = new MapEntry[BUCKET_SIZE + 1];
private final MapEntry head = new MapEntry<>(-1, null, null);
/**
* Creates new case-insensitive multimap.
*/
public static HttpMultiMap newCaseInsensitiveMap() {
return new HttpMultiMap<>(false);
}
/**
* Creates new case-insensitive map.
*/
public static HttpMultiMap newCaseSensitiveMap() {
return new HttpMultiMap<>(true);
}
protected HttpMultiMap(final boolean caseSensitive) {
head.before = head.after = head;
this.caseSensitive = caseSensitive;
}
/**
* Calculates hash value of the input string.
*/
private int hash(final String name) {
int h = 0;
for (int i = name.length() - 1; i >= 0; i--) {
char c = name.charAt(i);
if (!caseSensitive) {
if (c >= 'A' && c <= 'Z') {
c += 32;
}
}
h = 31 * h + c;
}
if (h > 0) {
return h;
}
if (h == Integer.MIN_VALUE) {
return Integer.MAX_VALUE;
}
return -h;
}
/**
* Calculates bucket index from the hash.
*/
private static int index(final int hash) {
return hash & BUCKET_SIZE;
}
/**
* Returns true
if two names are the same.
*/
private boolean eq(final String name1, final String name2) {
int nameLen = name1.length();
if (nameLen != name2.length()) {
return false;
}
for (int i = nameLen - 1; i >= 0; i--) {
char c1 = name1.charAt(i);
char c2 = name2.charAt(i);
if (c1 != c2) {
if (caseSensitive) {
return false;
}
if (c1 >= 'A' && c1 <= 'Z') {
c1 += 32;
}
if (c2 >= 'A' && c2 <= 'Z') {
c2 += 32;
}
if (c1 != c2) {
return false;
}
}
}
return true;
}
// ---------------------------------------------------------------- basic
/**
* Returns the number of keys. This is not the number of all elements.
* Not optimized.
*/
public int size() {
return names().size();
}
/**
* Clears the map.
*/
public HttpMultiMap clear() {
for (int i = 0; i < entries.length; i++) {
entries[i] = null;
}
head.before = head.after = head;
return this;
}
/**
* Returns true
if name exist.
*/
public boolean contains(final String name) {
return getEntry(name) != null;
}
/**
* Returns true
if map is empty.
*/
public boolean isEmpty() {
return head == head.after;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Map.Entry entry : this) {
sb.append(entry).append('\n');
}
return sb.toString();
}
// ---------------------------------------------------------------- set/add
private HttpMultiMap _set(final Iterable> map) {
clear();
for (Map.Entry entry : map) {
add(entry.getKey(), entry.getValue());
}
return this;
}
public HttpMultiMap setAll(final HttpMultiMap multiMap) {
return _set(multiMap);
}
public HttpMultiMap setAll(final Map map) {
return _set(map.entrySet());
}
public HttpMultiMap set(final String name, final V value) {
int h = hash(name);
int i = index(h);
_remove(h, i, name);
_add(h, i, name, value);
return this;
}
public HttpMultiMap setAll(final String name, final Iterable values) {
int h = hash(name);
int i = index(h);
_remove(h, i, name);
for (V v : values) {
_add(h, i, name, v);
}
return this;
}
public HttpMultiMap add(final String name, final V value) {
int h = hash(name);
int i = index(h);
_add(h, i, name, value);
return this;
}
public HttpMultiMap addAll(final String name, final Iterable values) {
int h = hash(name);
int i = index(h);
for (V value : values) {
_add(h, i, name, value);
}
return this;
}
public HttpMultiMap addAll(final HttpMultiMap map) {
for (Map.Entry entry : map.entries()) {
add(entry.getKey(), entry.getValue());
}
return this;
}
public HttpMultiMap addAll(final Map map) {
for (Map.Entry entry : map.entrySet()) {
add(entry.getKey(), entry.getValue());
}
return this;
}
private void _add(final int hash, final int index, final String name, final V value) {
// update the hash table
MapEntry e = entries[index];
MapEntry newEntry;
entries[index] = newEntry = new MapEntry<>(hash, name, value);
newEntry.next = e;
// update the linked list
newEntry.addBefore(head);
}
// ---------------------------------------------------------------- remove
public HttpMultiMap remove(final String name) {
int h = hash(name);
int i = index(h);
_remove(h, i, name);
return this;
}
private void _remove(final int hash, final int index, final String name) {
MapEntry e = entries[index];
if (e == null) {
return;
}
for (; ; ) {
if (e.hash == hash && eq(name, e.key)) {
e.remove();
MapEntry next = e.next;
if (next != null) {
entries[index] = next;
e = next;
}
else {
entries[index] = null;
return;
}
}
else {
break;
}
}
for (; ; ) {
MapEntry next = e.next;
if (next == null) {
break;
}
if (next.hash == hash && eq(name, next.key)) {
e.next = next.next;
next.remove();
}
else {
e = next;
}
}
}
// ---------------------------------------------------------------- get
/**
* Returns the first value from the map associated with the name.
* Returns null
if name does not exist or
* if associated value is null
.
*/
public V get(final String name) {
Map.Entry entry = getEntry(name);
if (entry == null) {
return null;
}
return entry.getValue();
}
/**
* Returns first entry for given name. Returns null
if entry
* does not exist.
*/
public Map.Entry getEntry(final String name) {
int h = hash(name);
int i = index(h);
MapEntry e = entries[i];
while (e != null) {
if (e.hash == h && eq(name, e.key)) {
return e;
}
e = e.next;
}
return null;
}
/**
* Returns all values associated with the name.
*/
public List getAll(final String name) {
LinkedList values = new LinkedList<>();
int h = hash(name);
int i = index(h);
MapEntry e = entries[i];
while (e != null) {
if (e.hash == h && eq(name, e.key)) {
values.addFirst(e.getValue());
}
e = e.next;
}
return values;
}
// ---------------------------------------------------------------- iterate
/**
* Returns iterator of all entries.
*/
@Override
public Iterator> iterator() {
final MapEntry[] e = {head.after};
return new Iterator>() {
@Override
public boolean hasNext() {
return e[0] != head;
}
@Override
@SuppressWarnings("unchecked")
public Map.Entry next() {
if (!hasNext()) {
throw new NoSuchElementException("No next() entry in the iteration");
}
MapEntry next = e[0];
e[0] = e[0].after;
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public Set names() {
Set names = new TreeSet<>(caseSensitive ? null : String.CASE_INSENSITIVE_ORDER);
MapEntry e = head.after;
while (e != head) {
names.add(e.getKey());
e = e.after;
}
return names;
}
/**
* Returns all the entries of this map. Case sensitivity does not influence
* the returned list, it always contains all of the values.
*/
public List> entries() {
List> all = new LinkedList<>();
MapEntry e = head.after;
while (e != head) {
all.add(e);
e = e.after;
}
return all;
}
private static final class MapEntry implements Map.Entry {
final int hash;
final String key;
V value;
MapEntry next;
MapEntry before, after;
private MapEntry(final int hash, final String key, final V value) {
this.hash = hash;
this.key = key;
this.value = value;
}
void remove() {
before.after = after;
after.before = before;
}
void addBefore(final MapEntry e) {
after = e;
before = e.before;
before.after = this;
after.before = this;
}
@Override
public String getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(final V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
@Override
public String toString() {
return getKey() + ": " + getValue();
}
}
}