io.undertow.util.SubstringMap Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* A string keyed map that can be accessed as a substring, eliminating the need to allocate a new string
* to do a key comparison against.
*
* This class uses linear probing and is thread safe due to copy on write semantics. As such it is not recomended
* for data that changes frequently.
*
* This class does not actually implement the map interface to avoid implementing unnecessary operations.
*
* @author Stuart Douglas
*/
public class SubstringMap {
private static final int ALL_BUT_LAST_BIT = ~1;
private volatile Object[] table = new Object[16];
private int size;
public SubstringMatch get(String key, int length) {
return get(key, length, false);
}
public SubstringMatch get(String key) {
return get(key, key.length(), false);
}
private SubstringMatch get(String key, int length, boolean exact) {
if(key.length() < length) {
throw new IllegalArgumentException();
}
Object[] table = this.table;
int hash = hash(key, length);
int pos = tablePos(table, hash);
int start = pos;
while (table[pos] != null) {
if(exact) {
if(table[pos].equals(key)) {
return (SubstringMatch) table[pos + 1];
}
} else {
if (doEquals((String) table[pos], key, length)) {
return (SubstringMatch) table[pos + 1];
}
}
pos += 2;
if(pos >= table.length) {
pos = 0;
}
if(pos == start) {
return null;
}
}
return null;
}
private int tablePos(Object[] table, int hash) {
return (hash & (table.length - 1)) & ALL_BUT_LAST_BIT;
}
private boolean doEquals(String s1, String s2, int length) {
if(s1.length() != length || s2.length() < length) {
return false;
}
for(int i = 0; i < length; ++i) {
if(s1.charAt(i) != s2.charAt(i)) {
return false;
}
}
return true;
}
public synchronized void put(String key, V value) {
if (key == null) {
throw new NullPointerException();
}
Object[] newTable;
if (table.length / (double) size < 4 && table.length != Integer.MAX_VALUE) {
newTable = new Object[table.length << 1];
for (int i = 0; i < table.length; i += 2) {
if (table[i] != null) {
doPut(newTable, (String) table[i], table[i + 1]);
}
}
} else {
newTable = new Object[table.length];
System.arraycopy(table, 0, newTable, 0, table.length);
}
doPut(newTable, key, new SubstringMap.SubstringMatch<>(key, value));
this.table = newTable;
size++;
}
public synchronized V remove(String key) {
if (key == null) {
throw new NullPointerException();
}
//we just assume it is present, and always do a copy
//for this maps intended use cases as a path matcher it won't be called when
//the value is not present anyway
V value = null;
Object[] newTable = new Object[table.length];
for (int i = 0; i < table.length; i += 2) {
if (table[i] != null && !table[i].equals(key)) {
doPut(newTable, (String) table[i], table[i + 1]);
} else if (table[i] != null) {
value = (V) table[i + 1];
size--;
}
}
this.table = newTable;
if(value == null) {
return null;
}
return ((SubstringMatch)value).getValue();
}
private void doPut(Object[] newTable, String key, Object value) {
int hash = hash(key, key.length());
int pos = tablePos(newTable, hash);
while (newTable[pos] != null && !newTable[pos].equals(key)) {
pos += 2;
if (pos >= newTable.length) {
pos = 0;
}
}
newTable[pos] = key;
newTable[pos + 1] = value;
}
public Map toMap() {
Map map = new HashMap<>();
Object[] t = this.table;
for(int i = 0; i < t.length; i += 2) {
if(t[i] != null) {
map.put((String)t[i], ((SubstringMatch)t[i+1]).value);
}
}
return map;
}
public synchronized void clear() {
size = 0;
table = new Object[16];
}
private static int hash(String value, int length) {
if (length == 0) {
return 0;
}
int h = 0;
for (int i = 0; i < length; i++) {
h = 31 * h + value.charAt(i);
}
return h;
}
public Iterable keys() {
return new Iterable() {
@Override
public Iterator iterator() {
final Object[] tMap = table;
int i = 0;
while (i < table.length && tMap[i] == null) {
i += 2;
}
final int startPos = i;
return new Iterator() {
private Object[] map = tMap;
private int pos = startPos;
@Override
public boolean hasNext() {
return pos < table.length;
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String ret = (String) map[pos];
pos += 2;
while (pos < table.length && tMap[pos] == null) {
pos += 2;
}
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
public static final class SubstringMatch {
private final String key;
private final V value;
public SubstringMatch(String key, V value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public V getValue() {
return value;
}
}
}