Tries improvements (#5736)

* ArrayTrie handles full alphabet
* TreeTrie handles case sensitive
* improved trie selection from Index builders
* optimisations, cleanups and benchmarks.

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2021-01-05 12:52:34 +01:00 committed by GitHub
parent 480767a03b
commit 51120b1f0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1951 additions and 1019 deletions

View File

@ -145,18 +145,16 @@ public class HttpField
return false; return false;
if (_value == null) if (_value == null)
return false; return false;
if (search.equals(_value)) if (search.equalsIgnoreCase(_value))
return true; return true;
search = StringUtil.asciiToLowerCase(search);
int state = 0; int state = 0;
int match = 0; int match = 0;
int param = 0; int param = 0;
for (int i = 0; i < _value.length(); i++) for (int i = 0; i < _value.length(); i++)
{ {
char c = _value.charAt(i); char c = StringUtil.asciiToLowerCase(_value.charAt(i));
switch (state) switch (state)
{ {
case 0: // initial white space case 0: // initial white space
@ -181,7 +179,7 @@ public class HttpField
break; break;
default: // character default: // character
match = Character.toLowerCase(c) == search.charAt(0) ? 1 : -1; match = c == StringUtil.asciiToLowerCase(search.charAt(0)) ? 1 : -1;
state = 1; state = 1;
break; break;
} }
@ -206,7 +204,7 @@ public class HttpField
if (match > 0) if (match > 0)
{ {
if (match < search.length()) if (match < search.length())
match = Character.toLowerCase(c) == search.charAt(match) ? (match + 1) : -1; match = c == StringUtil.asciiToLowerCase(search.charAt(match)) ? (match + 1) : -1;
else if (c != ' ' && c != '\t') else if (c != ' ' && c != '\t')
match = -1; match = -1;
} }
@ -229,7 +227,7 @@ public class HttpField
if (match >= 0) if (match >= 0)
{ {
if (match < search.length()) if (match < search.length())
match = Character.toLowerCase(c) == search.charAt(match) ? (match + 1) : -1; match = c == StringUtil.asciiToLowerCase(search.charAt(match)) ? (match + 1) : -1;
else else
match = -1; match = -1;
} }
@ -240,7 +238,7 @@ public class HttpField
if (match >= 0) if (match >= 0)
{ {
if (match < search.length()) if (match < search.length())
match = Character.toLowerCase(c) == search.charAt(match) ? (match + 1) : -1; match = c == StringUtil.asciiToLowerCase(search.charAt(match)) ? (match + 1) : -1;
else else
match = -1; match = -1;
} }
@ -290,7 +288,7 @@ public class HttpField
if (param >= 0) if (param >= 0)
{ {
if (param < __zeroquality.length()) if (param < __zeroquality.length())
param = Character.toLowerCase(c) == __zeroquality.charAt(param) ? (param + 1) : -1; param = c == __zeroquality.charAt(param) ? (param + 1) : -1;
else if (c != '0' && c != '.') else if (c != '0' && c != '.')
param = -1; param = -1;
} }

View File

@ -15,9 +15,11 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.function.Function;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Index; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
/** /**
* *
@ -71,7 +73,7 @@ public enum HttpHeaderValue
return _string; return _string;
} }
private static EnumSet<HttpHeader> __known = private static final EnumSet<HttpHeader> __known =
EnumSet.of(HttpHeader.CONNECTION, EnumSet.of(HttpHeader.CONNECTION,
HttpHeader.TRANSFER_ENCODING, HttpHeader.TRANSFER_ENCODING,
HttpHeader.CONTENT_ENCODING); HttpHeader.CONTENT_ENCODING);
@ -82,4 +84,89 @@ public enum HttpHeaderValue
return false; return false;
return __known.contains(header); return __known.contains(header);
} }
/**
* Parse an unquoted comma separated list of index keys.
* @param value A string list of index keys, separated with commas and possible white space
* @param found The function to call for all found index entries. If the function returns false parsing is halted.
* @return true if parsing completed normally and all found index items returned true from the found function.
*/
public static boolean parseCsvIndex(String value, Function<HttpHeaderValue, Boolean> found)
{
return parseCsvIndex(value, found, null);
}
/**
* Parse an unquoted comma separated list of index keys.
* @param value A string list of index keys, separated with commas and possible white space
* @param found The function to call for all found index entries. If the function returns false parsing is halted.
* @param unknown The function to call for foound unknown entries. If the function returns false parsing is halted.
* @return true if parsing completed normally and all found index items returned true from the found function.
*/
public static boolean parseCsvIndex(String value, Function<HttpHeaderValue, Boolean> found, Function<String, Boolean> unknown)
{
if (StringUtil.isBlank(value))
return true;
int next = 0;
parsing: while (next < value.length())
{
// Look for the best fit next token
HttpHeaderValue token = CACHE.getBest(value, next, value.length() - next);
// if a token is found
if (token != null)
{
// check that it is only followed by whatspace, EOL and/or comma
int i = next + token.toString().length();
loop: while (true)
{
if (i >= value.length())
return found.apply(token);
switch (value.charAt(i))
{
case ',':
if (!found.apply(token))
return false;
next = i + 1;
continue parsing;
case ' ':
break;
default:
break loop;
}
i++;
}
}
// Token was not correctly matched
if (' ' == value.charAt(next))
{
next++;
continue;
}
int comma = value.indexOf(',', next);
if (comma == next)
{
next++;
continue;
}
else if (comma > next)
{
if (unknown == null)
{
next = comma + 1;
continue;
}
String v = value.substring(next, comma).trim();
if (StringUtil.isBlank(v) || unknown.apply(v))
{
next = comma + 1;
continue;
}
}
return false;
}
return true;
}
} }

View File

@ -109,7 +109,6 @@ public class HttpParser
.with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip")) .with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip"))
.with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate")) .with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate"))
.with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate, br")) .with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate, br"))
.with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip,deflate,sdch"))
.with(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-US,enq=0.5")) .with(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-US,enq=0.5"))
.with(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-GB,en-USq=0.8,enq=0.6")) .with(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-GB,en-USq=0.8,enq=0.6"))
.with(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-AU,enq=0.9,it-ITq=0.8,itq=0.7,en-GBq=0.6,en-USq=0.5")) .with(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-AU,enq=0.9,it-ITq=0.8,itq=0.7,en-GBq=0.6,en-USq=0.5"))
@ -1058,19 +1057,11 @@ public class HttpParser
if (addToFieldCache && _header != null && _valueString != null) if (addToFieldCache && _header != null && _valueString != null)
{ {
if (_fieldCache == null) if (_fieldCache == null)
{ _fieldCache = Index.buildCaseSensitiveMutableVisibleAsciiAlphabet(getHeaderCacheSize());
_fieldCache = (getHeaderCacheSize() > 0 && (_version != null && _version == HttpVersion.HTTP_1_1))
? new Index.Builder<HttpField>()
.caseSensitive(false)
.mutable()
.maxCapacity(getHeaderCacheSize())
.build()
: NO_CACHE;
}
if (_field == null) if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString); _field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
if (!_fieldCache.put(_field)) if (_field.getValue().length() < getHeaderCacheSize() && !_fieldCache.put(_field))
{ {
_fieldCache.clear(); _fieldCache.clear();
_fieldCache.put(_field); _fieldCache.put(_field);

View File

@ -37,6 +37,7 @@ import static org.eclipse.jetty.http.HttpCompliance.Violation.TRANSFER_ENCODING_
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -2971,4 +2972,82 @@ public class HttpParserTest
_complianceViolation.add(violation); _complianceViolation.add(violation);
} }
} }
@Test
public void testHttpHeaderValueParseCsv()
{
final List<HttpHeaderValue> list = new ArrayList<>();
final List<String> unknowns = new ArrayList<>();
assertTrue(HttpHeaderValue.parseCsvIndex("", list::add, unknowns::add));
assertThat(list, empty());
assertThat(unknowns, empty());
assertTrue(HttpHeaderValue.parseCsvIndex(" ", list::add, unknowns::add));
assertThat(list, empty());
assertThat(unknowns, empty());
assertTrue(HttpHeaderValue.parseCsvIndex(",", list::add, unknowns::add));
assertThat(list, empty());
assertThat(unknowns, empty());
assertTrue(HttpHeaderValue.parseCsvIndex(",,", list::add, unknowns::add));
assertThat(list, empty());
assertThat(unknowns, empty());
assertTrue(HttpHeaderValue.parseCsvIndex(" , , ", list::add, unknowns::add));
assertThat(list, empty());
assertThat(unknowns, empty());
list.clear();
assertTrue(HttpHeaderValue.parseCsvIndex("close", list::add));
assertThat(list, contains(HttpHeaderValue.CLOSE));
list.clear();
assertTrue(HttpHeaderValue.parseCsvIndex(" close ", list::add));
assertThat(list, contains(HttpHeaderValue.CLOSE));
list.clear();
assertTrue(HttpHeaderValue.parseCsvIndex(",close,", list::add));
assertThat(list, contains(HttpHeaderValue.CLOSE));
list.clear();
assertTrue(HttpHeaderValue.parseCsvIndex(" , close , ", list::add));
assertThat(list, contains(HttpHeaderValue.CLOSE));
list.clear();
assertTrue(HttpHeaderValue.parseCsvIndex(" close,GZIP, chunked , Keep-Alive ", list::add));
assertThat(list, contains(HttpHeaderValue.CLOSE, HttpHeaderValue.GZIP, HttpHeaderValue.CHUNKED, HttpHeaderValue.KEEP_ALIVE));
list.clear();
assertTrue(HttpHeaderValue.parseCsvIndex(" close,GZIP, chunked , Keep-Alive ", t ->
{
if (t.toString().startsWith("c"))
list.add(t);
return true;
}));
assertThat(list, contains(HttpHeaderValue.CLOSE, HttpHeaderValue.CHUNKED));
list.clear();
assertFalse(HttpHeaderValue.parseCsvIndex(" close,GZIP, chunked , Keep-Alive ", t ->
{
if (HttpHeaderValue.CHUNKED == t)
return false;
list.add(t);
return true;
}));
assertThat(list, contains(HttpHeaderValue.CLOSE, HttpHeaderValue.GZIP));
list.clear();
unknowns.clear();
assertTrue(HttpHeaderValue.parseCsvIndex("closed,close, unknown , bytes", list::add, unknowns::add));
assertThat(list, contains(HttpHeaderValue.CLOSE, HttpHeaderValue.BYTES));
assertThat(unknowns, contains("closed", "unknown"));
list.clear();
unknowns.clear();
assertFalse(HttpHeaderValue.parseCsvIndex("close, unknown , bytes", list::add, s -> false));
assertThat(list, contains(HttpHeaderValue.CLOSE));
assertThat(unknowns, empty());
}
} }

View File

@ -14,311 +14,312 @@
package org.eclipse.jetty.http2; package org.eclipse.jetty.http2;
import java.util.Comparator; import java.util.Comparator;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.util.Index; import org.eclipse.jetty.util.StringUtil;
public class HTTP2Cipher public class HTTP2Cipher
{ {
public static final Comparator<String> COMPARATOR = new CipherComparator(); public static final Comparator<String> COMPARATOR = new CipherComparator();
private static final Index<Boolean> __blackProtocols = new Index.Builder<Boolean>() private static final Set<String> __blackProtocols = Stream.of(
.caseSensitive(false) "TLSv1.2",
.with("TLSv1.2", Boolean.TRUE) "TLSv1.1",
.with("TLSv1.1", Boolean.TRUE) "TLSv1",
.with("TLSv1", Boolean.TRUE) "SSL",
.with("SSL", Boolean.TRUE) "SSLv2",
.with("SSLv2", Boolean.TRUE) "SSLv3"
.with("SSLv3", Boolean.TRUE) ).map(StringUtil::asciiToUpperCase).collect(Collectors.toSet());
.build();
private static final Index<Boolean> __blackCiphers = new Index.Builder<Boolean>() private static final Set<String> __blackCiphers = Stream.of(
.caseSensitive(false) "TLS_NULL_WITH_NULL_NULL",
.with("TLS_NULL_WITH_NULL_NULL", Boolean.TRUE) "TLS_RSA_WITH_NULL_MD5",
.with("TLS_RSA_WITH_NULL_MD5", Boolean.TRUE) "TLS_RSA_WITH_NULL_SHA",
.with("TLS_RSA_WITH_NULL_SHA", Boolean.TRUE) "TLS_RSA_EXPORT_WITH_RC4_40_MD5",
.with("TLS_RSA_EXPORT_WITH_RC4_40_MD5", Boolean.TRUE) "TLS_RSA_WITH_RC4_128_MD5",
.with("TLS_RSA_WITH_RC4_128_MD5", Boolean.TRUE) "TLS_RSA_WITH_RC4_128_SHA",
.with("TLS_RSA_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
.with("TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", Boolean.TRUE) "TLS_RSA_WITH_IDEA_CBC_SHA",
.with("TLS_RSA_WITH_IDEA_CBC_SHA", Boolean.TRUE) "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA",
.with("TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE) "TLS_RSA_WITH_DES_CBC_SHA",
.with("TLS_RSA_WITH_DES_CBC_SHA", Boolean.TRUE) "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
.with("TLS_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA",
.with("TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE) "TLS_DH_DSS_WITH_DES_CBC_SHA",
.with("TLS_DH_DSS_WITH_DES_CBC_SHA", Boolean.TRUE) "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA",
.with("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA",
.with("TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE) "TLS_DH_RSA_WITH_DES_CBC_SHA",
.with("TLS_DH_RSA_WITH_DES_CBC_SHA", Boolean.TRUE) "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA",
.with("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
.with("TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE) "TLS_DHE_DSS_WITH_DES_CBC_SHA",
.with("TLS_DHE_DSS_WITH_DES_CBC_SHA", Boolean.TRUE) "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
.with("TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
.with("TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE) "TLS_DHE_RSA_WITH_DES_CBC_SHA",
.with("TLS_DHE_RSA_WITH_DES_CBC_SHA", Boolean.TRUE) "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
.with("TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5",
.with("TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", Boolean.TRUE) "TLS_DH_anon_WITH_RC4_128_MD5",
.with("TLS_DH_anon_WITH_RC4_128_MD5", Boolean.TRUE) "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
.with("TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE) "TLS_DH_anon_WITH_DES_CBC_SHA",
.with("TLS_DH_anon_WITH_DES_CBC_SHA", Boolean.TRUE) "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA",
.with("TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_KRB5_WITH_DES_CBC_SHA",
.with("TLS_KRB5_WITH_DES_CBC_SHA", Boolean.TRUE) "TLS_KRB5_WITH_3DES_EDE_CBC_SHA",
.with("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_KRB5_WITH_RC4_128_SHA",
.with("TLS_KRB5_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_KRB5_WITH_IDEA_CBC_SHA",
.with("TLS_KRB5_WITH_IDEA_CBC_SHA", Boolean.TRUE) "TLS_KRB5_WITH_DES_CBC_MD5",
.with("TLS_KRB5_WITH_DES_CBC_MD5", Boolean.TRUE) "TLS_KRB5_WITH_3DES_EDE_CBC_MD5",
.with("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", Boolean.TRUE) "TLS_KRB5_WITH_RC4_128_MD5",
.with("TLS_KRB5_WITH_RC4_128_MD5", Boolean.TRUE) "TLS_KRB5_WITH_IDEA_CBC_MD5",
.with("TLS_KRB5_WITH_IDEA_CBC_MD5", Boolean.TRUE) "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA",
.with("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", Boolean.TRUE) "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA",
.with("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", Boolean.TRUE) "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
.with("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", Boolean.TRUE) "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5",
.with("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", Boolean.TRUE) "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5",
.with("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", Boolean.TRUE) "TLS_KRB5_EXPORT_WITH_RC4_40_MD5",
.with("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", Boolean.TRUE) "TLS_PSK_WITH_NULL_SHA",
.with("TLS_PSK_WITH_NULL_SHA", Boolean.TRUE) "TLS_DHE_PSK_WITH_NULL_SHA",
.with("TLS_DHE_PSK_WITH_NULL_SHA", Boolean.TRUE) "TLS_RSA_PSK_WITH_NULL_SHA",
.with("TLS_RSA_PSK_WITH_NULL_SHA", Boolean.TRUE) "TLS_RSA_WITH_AES_128_CBC_SHA",
.with("TLS_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_DH_DSS_WITH_AES_128_CBC_SHA",
.with("TLS_DH_DSS_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_DH_RSA_WITH_AES_128_CBC_SHA",
.with("TLS_DH_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
.with("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
.with("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_DH_anon_WITH_AES_128_CBC_SHA",
.with("TLS_DH_anon_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_RSA_WITH_AES_256_CBC_SHA",
.with("TLS_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_DH_DSS_WITH_AES_256_CBC_SHA",
.with("TLS_DH_DSS_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_DH_RSA_WITH_AES_256_CBC_SHA",
.with("TLS_DH_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
.with("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
.with("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_DH_anon_WITH_AES_256_CBC_SHA",
.with("TLS_DH_anon_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_RSA_WITH_NULL_SHA256",
.with("TLS_RSA_WITH_NULL_SHA256", Boolean.TRUE) "TLS_RSA_WITH_AES_128_CBC_SHA256",
.with("TLS_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_RSA_WITH_AES_256_CBC_SHA256",
.with("TLS_RSA_WITH_AES_256_CBC_SHA256", Boolean.TRUE) "TLS_DH_DSS_WITH_AES_128_CBC_SHA256",
.with("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_DH_RSA_WITH_AES_128_CBC_SHA256",
.with("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
.with("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA",
.with("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE) "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA",
.with("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE) "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA",
.with("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE) "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA",
.with("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE) "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA",
.with("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE) "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA",
.with("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE) "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
.with("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_DH_DSS_WITH_AES_256_CBC_SHA256",
.with("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", Boolean.TRUE) "TLS_DH_RSA_WITH_AES_256_CBC_SHA256",
.with("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", Boolean.TRUE) "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
.with("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", Boolean.TRUE) "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
.with("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", Boolean.TRUE) "TLS_DH_anon_WITH_AES_128_CBC_SHA256",
.with("TLS_DH_anon_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_DH_anon_WITH_AES_256_CBC_SHA256",
.with("TLS_DH_anon_WITH_AES_256_CBC_SHA256", Boolean.TRUE) "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA",
.with("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE) "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA",
.with("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE) "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA",
.with("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE) "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA",
.with("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE) "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA",
.with("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE) "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA",
.with("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE) "TLS_PSK_WITH_RC4_128_SHA",
.with("TLS_PSK_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_PSK_WITH_3DES_EDE_CBC_SHA",
.with("TLS_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_PSK_WITH_AES_128_CBC_SHA",
.with("TLS_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_PSK_WITH_AES_256_CBC_SHA",
.with("TLS_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_DHE_PSK_WITH_RC4_128_SHA",
.with("TLS_DHE_PSK_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA",
.with("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_DHE_PSK_WITH_AES_128_CBC_SHA",
.with("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_DHE_PSK_WITH_AES_256_CBC_SHA",
.with("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_RSA_PSK_WITH_RC4_128_SHA",
.with("TLS_RSA_PSK_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA",
.with("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_RSA_PSK_WITH_AES_128_CBC_SHA",
.with("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_RSA_PSK_WITH_AES_256_CBC_SHA",
.with("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_RSA_WITH_SEED_CBC_SHA",
.with("TLS_RSA_WITH_SEED_CBC_SHA", Boolean.TRUE) "TLS_DH_DSS_WITH_SEED_CBC_SHA",
.with("TLS_DH_DSS_WITH_SEED_CBC_SHA", Boolean.TRUE) "TLS_DH_RSA_WITH_SEED_CBC_SHA",
.with("TLS_DH_RSA_WITH_SEED_CBC_SHA", Boolean.TRUE) "TLS_DHE_DSS_WITH_SEED_CBC_SHA",
.with("TLS_DHE_DSS_WITH_SEED_CBC_SHA", Boolean.TRUE) "TLS_DHE_RSA_WITH_SEED_CBC_SHA",
.with("TLS_DHE_RSA_WITH_SEED_CBC_SHA", Boolean.TRUE) "TLS_DH_anon_WITH_SEED_CBC_SHA",
.with("TLS_DH_anon_WITH_SEED_CBC_SHA", Boolean.TRUE) "TLS_RSA_WITH_AES_128_GCM_SHA256",
.with("TLS_RSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE) "TLS_RSA_WITH_AES_256_GCM_SHA384",
.with("TLS_RSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE) "TLS_DH_RSA_WITH_AES_128_GCM_SHA256",
.with("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE) "TLS_DH_RSA_WITH_AES_256_GCM_SHA384",
.with("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE) "TLS_DH_DSS_WITH_AES_128_GCM_SHA256",
.with("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", Boolean.TRUE) "TLS_DH_DSS_WITH_AES_256_GCM_SHA384",
.with("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", Boolean.TRUE) "TLS_DH_anon_WITH_AES_128_GCM_SHA256",
.with("TLS_DH_anon_WITH_AES_128_GCM_SHA256", Boolean.TRUE) "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
.with("TLS_DH_anon_WITH_AES_256_GCM_SHA384", Boolean.TRUE) "TLS_PSK_WITH_AES_128_GCM_SHA256",
.with("TLS_PSK_WITH_AES_128_GCM_SHA256", Boolean.TRUE) "TLS_PSK_WITH_AES_256_GCM_SHA384",
.with("TLS_PSK_WITH_AES_256_GCM_SHA384", Boolean.TRUE) "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256",
.with("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", Boolean.TRUE) "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384",
.with("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", Boolean.TRUE) "TLS_PSK_WITH_AES_128_CBC_SHA256",
.with("TLS_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_PSK_WITH_AES_256_CBC_SHA384",
.with("TLS_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE) "TLS_PSK_WITH_NULL_SHA256",
.with("TLS_PSK_WITH_NULL_SHA256", Boolean.TRUE) "TLS_PSK_WITH_NULL_SHA384",
.with("TLS_PSK_WITH_NULL_SHA384", Boolean.TRUE) "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256",
.with("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384",
.with("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE) "TLS_DHE_PSK_WITH_NULL_SHA256",
.with("TLS_DHE_PSK_WITH_NULL_SHA256", Boolean.TRUE) "TLS_DHE_PSK_WITH_NULL_SHA384",
.with("TLS_DHE_PSK_WITH_NULL_SHA384", Boolean.TRUE) "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256",
.with("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384",
.with("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE) "TLS_RSA_PSK_WITH_NULL_SHA256",
.with("TLS_RSA_PSK_WITH_NULL_SHA256", Boolean.TRUE) "TLS_RSA_PSK_WITH_NULL_SHA384",
.with("TLS_RSA_PSK_WITH_NULL_SHA384", Boolean.TRUE) "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256",
.with("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE) "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256",
.with("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE) "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256",
.with("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE) "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256",
.with("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE) "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256",
.with("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE) "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256",
.with("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE) "TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
.with("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_NULL_SHA",
.with("TLS_ECDH_ECDSA_WITH_NULL_SHA", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
.with("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
.with("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
.with("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
.with("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_NULL_SHA",
.with("TLS_ECDHE_ECDSA_WITH_NULL_SHA", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
.with("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
.with("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
.with("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
.with("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_ECDH_RSA_WITH_NULL_SHA",
.with("TLS_ECDH_RSA_WITH_NULL_SHA", Boolean.TRUE) "TLS_ECDH_RSA_WITH_RC4_128_SHA",
.with("TLS_ECDH_RSA_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
.with("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
.with("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
.with("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_NULL_SHA",
.with("TLS_ECDHE_RSA_WITH_NULL_SHA", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
.with("TLS_ECDHE_RSA_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
.with("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
.with("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
.with("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_ECDH_anon_WITH_NULL_SHA",
.with("TLS_ECDH_anon_WITH_NULL_SHA", Boolean.TRUE) "TLS_ECDH_anon_WITH_RC4_128_SHA",
.with("TLS_ECDH_anon_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
.with("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
.with("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
.with("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA",
.with("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA",
.with("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA",
.with("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_SRP_SHA_WITH_AES_128_CBC_SHA",
.with("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA",
.with("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA",
.with("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_SRP_SHA_WITH_AES_256_CBC_SHA",
.with("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA",
.with("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA",
.with("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
.with("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
.with("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
.with("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
.with("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
.with("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
.with("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
.with("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
.with("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
.with("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
.with("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE) "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
.with("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE) "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
.with("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_RC4_128_SHA",
.with("TLS_ECDHE_PSK_WITH_RC4_128_SHA", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA",
.with("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA",
.with("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA",
.with("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256",
.with("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384",
.with("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_NULL_SHA",
.with("TLS_ECDHE_PSK_WITH_NULL_SHA", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_NULL_SHA256",
.with("TLS_ECDHE_PSK_WITH_NULL_SHA256", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_NULL_SHA384",
.with("TLS_ECDHE_PSK_WITH_NULL_SHA384", Boolean.TRUE) "TLS_RSA_WITH_ARIA_128_CBC_SHA256",
.with("TLS_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_RSA_WITH_ARIA_256_CBC_SHA384",
.with("TLS_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256",
.with("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384",
.with("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256",
.with("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384",
.with("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256",
.with("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384",
.with("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256",
.with("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384",
.with("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256",
.with("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384",
.with("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256",
.with("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384",
.with("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256",
.with("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384",
.with("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256",
.with("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384",
.with("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256",
.with("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384",
.with("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_RSA_WITH_ARIA_128_GCM_SHA256",
.with("TLS_RSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE) "TLS_RSA_WITH_ARIA_256_GCM_SHA384",
.with("TLS_RSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE) "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256",
.with("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE) "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384",
.with("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE) "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256",
.with("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE) "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384",
.with("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE) "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256",
.with("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE) "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384",
.with("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256",
.with("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384",
.with("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE) "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256",
.with("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE) "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384",
.with("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE) "TLS_PSK_WITH_ARIA_128_CBC_SHA256",
.with("TLS_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_PSK_WITH_ARIA_256_CBC_SHA384",
.with("TLS_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256",
.with("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384",
.with("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256",
.with("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384",
.with("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_PSK_WITH_ARIA_128_GCM_SHA256",
.with("TLS_PSK_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE) "TLS_PSK_WITH_ARIA_256_GCM_SHA384",
.with("TLS_PSK_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE) "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256",
.with("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE) "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384",
.with("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256",
.with("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384",
.with("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
.with("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
.with("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384",
.with("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384",
.with("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE) "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256",
.with("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE) "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384",
.with("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE) "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
.with("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE) "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
.with("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE) "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256",
.with("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE) "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384",
.with("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE) "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256",
.with("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE) "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384",
.with("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256",
.with("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE) "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384",
.with("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE) "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
.with("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE) "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
.with("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE) "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256",
.with("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE) "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384",
.with("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE) "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256",
.with("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE) "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384",
.with("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE) "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384",
.with("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE) "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
.with("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE) "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384",
.with("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
.with("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE) "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
.with("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE) "TLS_RSA_WITH_AES_128_CCM",
.with("TLS_RSA_WITH_AES_128_CCM", Boolean.TRUE) "TLS_RSA_WITH_AES_256_CCM",
.with("TLS_RSA_WITH_AES_256_CCM", Boolean.TRUE) "TLS_RSA_WITH_AES_128_CCM_8",
.with("TLS_RSA_WITH_AES_128_CCM_8", Boolean.TRUE) "TLS_RSA_WITH_AES_256_CCM_8",
.with("TLS_RSA_WITH_AES_256_CCM_8", Boolean.TRUE) "TLS_PSK_WITH_AES_128_CCM",
.with("TLS_PSK_WITH_AES_128_CCM", Boolean.TRUE) "TLS_PSK_WITH_AES_256_CCM",
.with("TLS_PSK_WITH_AES_256_CCM", Boolean.TRUE) "TLS_PSK_WITH_AES_128_CCM_8",
.with("TLS_PSK_WITH_AES_128_CCM_8", Boolean.TRUE) "TLS_PSK_WITH_AES_256_CCM_8"
.with("TLS_PSK_WITH_AES_256_CCM_8", Boolean.TRUE) ).map(StringUtil::asciiToUpperCase).collect(Collectors.toSet());
.build();
public static boolean isBlackListProtocol(String tlsProtocol) public static boolean isBlackListProtocol(String tlsProtocol)
{ {
return __blackProtocols.get(tlsProtocol) != null; return __blackProtocols.contains(StringUtil.asciiToUpperCase(tlsProtocol));
} }
public static boolean isBlackListCipher(String tlsCipher) public static boolean isBlackListCipher(String tlsCipher)
{ {
return __blackCiphers.get(tlsCipher) != null; return __blackCiphers.contains(StringUtil.asciiToUpperCase(tlsCipher));
} }
/** /**

View File

@ -500,31 +500,24 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
case EXPECT: case EXPECT:
{ {
if (HttpVersion.HTTP_1_1.equals(_requestBuilder.version())) if (!HttpHeaderValue.parseCsvIndex(value, t ->
{ {
HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value); switch (t)
if (expect == HttpHeaderValue.CONTINUE)
{ {
_expect100Continue = true; case CONTINUE:
} _expect100Continue = true;
else if (expect == HttpHeaderValue.PROCESSING) return true;
{ case PROCESSING:
_expect102Processing = true; _expect102Processing = true;
} return true;
else default:
{ return false;
String[] values = field.getValues();
for (int i = 0; values != null && i < values.length; i++)
{
expect = HttpHeaderValue.CACHE.get(values[i].trim());
if (expect == HttpHeaderValue.CONTINUE)
_expect100Continue = true;
else if (expect == HttpHeaderValue.PROCESSING)
_expect102Processing = true;
else
_unknownExpectation = true;
}
} }
}, s -> false))
{
_unknownExpectation = true;
_expect100Continue = false;
_expect102Processing = false;
} }
break; break;
} }

View File

@ -171,7 +171,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
} }
} }
private static Logger LOG = LoggerFactory.getLogger(HttpOutput.class); private static final Logger LOG = LoggerFactory.getLogger(HttpOutput.class);
private static final ThreadLocal<CharsetEncoder> _encoder = new ThreadLocal<>(); private static final ThreadLocal<CharsetEncoder> _encoder = new ThreadLocal<>();
private final HttpChannel _channel; private final HttpChannel _channel;
@ -1019,6 +1019,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (isClosed()) if (isClosed())
throw new IOException("Closed"); throw new IOException("Closed");
s = String.valueOf(s);
String charset = _channel.getResponse().getCharacterEncoding(); String charset = _channel.getResponse().getCharacterEncoding();
CharsetEncoder encoder = _encoder.get(); CharsetEncoder encoder = _encoder.get();
if (encoder == null || !encoder.charset().name().equalsIgnoreCase(charset)) if (encoder == null || !encoder.charset().name().equalsIgnoreCase(charset))

View File

@ -288,7 +288,7 @@ public class Request implements HttpServletRequest
return !isPush() && getHttpChannel().getHttpTransport().isPushSupported(); return !isPush() && getHttpChannel().getHttpTransport().isPushSupported();
} }
private static EnumSet<HttpHeader> NOT_PUSHED_HEADERS = EnumSet.of( private static final EnumSet<HttpHeader> NOT_PUSHED_HEADERS = EnumSet.of(
HttpHeader.IF_MATCH, HttpHeader.IF_MATCH,
HttpHeader.IF_RANGE, HttpHeader.IF_RANGE,
HttpHeader.IF_UNMODIFIED_SINCE, HttpHeader.IF_UNMODIFIED_SINCE,
@ -853,7 +853,7 @@ public class Request implements HttpServletRequest
public long getDateHeader(String name) public long getDateHeader(String name)
{ {
HttpFields fields = _httpFields; HttpFields fields = _httpFields;
return fields == null ? null : fields.getDateField(name); return fields == null ? -1 : fields.getDateField(name);
} }
@Override @Override
@ -1062,7 +1062,7 @@ public class Request implements HttpServletRequest
List<String> vals = getParameters().getValues(name); List<String> vals = getParameters().getValues(name);
if (vals == null) if (vals == null)
return null; return null;
return vals.toArray(new String[vals.size()]); return vals.toArray(new String[0]);
} }
public MultiMap<String> getQueryParameters() public MultiMap<String> getQueryParameters()

View File

@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -31,16 +32,21 @@ import java.util.stream.Collectors;
*/ */
abstract class AbstractTrie<V> implements Index.Mutable<V> abstract class AbstractTrie<V> implements Index.Mutable<V>
{ {
final boolean _caseInsensitive; final boolean _caseSensitive;
protected AbstractTrie(boolean insensitive) protected AbstractTrie(boolean caseSensitive)
{ {
_caseInsensitive = insensitive; _caseSensitive = caseSensitive;
} }
public boolean isCaseInsensitive() public boolean isCaseInsensitive()
{ {
return _caseInsensitive; return !_caseSensitive;
}
public boolean isCaseSensitive()
{
return _caseSensitive;
} }
public boolean put(V v) public boolean put(V v)
@ -84,16 +90,27 @@ abstract class AbstractTrie<V> implements Index.Mutable<V>
* <li>utf16</li> * <li>utf16</li>
* <li>utf8</li> * <li>utf8</li>
* </ul> * </ul>
* The tree has 10 nodes as follows: * The tree switching by character is:
* <pre> * <pre>
* 1 - 6 * 1 - 6
* / * /
* _ - 8 * _ - 8
* / * /
* u - t - f - 1 - 6 * root - u - t - f - 1 - 6
* \ * \
* 8 * 8
* </pre> * </pre>
* The count also applies to ternary trees as follows:
* <pre>
* root - u - t - f - _ ----- 1 - 6
* \ \
* 1 - 6 8
* \
* 8
* </pre>
* In both cases above there are 10 character nodes plus the root node that can
* hold a value for the empty string key, so the returned capacity is 11.
*
* @param keys The keys to be put in a Trie * @param keys The keys to be put in a Trie
* @param caseSensitive true if the capacity should be calculated with case-sensitive keys * @param caseSensitive true if the capacity should be calculated with case-sensitive keys
* @return The capacity in nodes of a tree decomposition * @return The capacity in nodes of a tree decomposition
@ -104,7 +121,7 @@ abstract class AbstractTrie<V> implements Index.Mutable<V>
? new ArrayList<>(keys) ? new ArrayList<>(keys)
: keys.stream().map(String::toLowerCase).collect(Collectors.toList()); : keys.stream().map(String::toLowerCase).collect(Collectors.toList());
Collections.sort(list); Collections.sort(list);
return AbstractTrie.requiredCapacity(list, 0, list.size(), 0); return 1 + AbstractTrie.requiredCapacity(list, 0, list.size(), 0);
} }
/** /**
@ -119,45 +136,62 @@ abstract class AbstractTrie<V> implements Index.Mutable<V>
{ {
int required = 0; int required = 0;
// Examine all the keys in the subtree while (true)
Character nodeChar = null;
for (int i = 0; i < length; i++)
{ {
String k = keys.get(offset + i); // Examine all the keys in the subtree
Character nodeChar = null;
for (int i = 0; i < length; i++)
{
String k = keys.get(offset + i);
// If the key is shorter than our current index then ignore it // If the key is shorter than our current index then ignore it
if (k.length() <= index) if (k.length() <= index)
continue; continue;
// Get the character at the index of the current key // Get the character at the index of the current key
char c = k.charAt(index); char c = k.charAt(index);
// If the character is the same as the current node, then we are // If the character is the same as the current node, then we are
// still in the current node and need to continue searching for the // still in the current node and need to continue searching for the
// next node or the end of the keys // next node or the end of the keys
if (nodeChar != null && c == nodeChar) if (nodeChar != null && c == nodeChar)
continue; continue;
// The character is a new node, so increase required by 1 // The character is a new node, so increase required by 1
required++; required++;
// if we had a previous node, then add the required nodes for the subtree under it. // if we had a previous node, then add the required nodes for the subtree under it.
if (nodeChar != null)
required += AbstractTrie.requiredCapacity(keys, offset, i, index + 1);
// set the char for the new node
nodeChar = c;
// reset the offset, length and index to continue iteration from the start of the new node
offset += i;
length -= i;
i = 0;
}
// If we finish the iteration with a nodeChar, then we must add the required nodes for the subtree under it.
if (nodeChar != null) if (nodeChar != null)
required += AbstractTrie.requiredCapacity(keys, offset, i, index + 1); {
// instead of recursion here, we loop to avoid tail recursion
index++;
continue;
}
// set the char for the new node return required;
nodeChar = c;
// reset the offset, length and index to continue iteration from the start of the new node
offset += i;
length -= i;
i = 0;
} }
}
// If we finish the iteration with a nodeChar, then we must add the required nodes for the subtree under it. protected boolean putAll(Map<String, V> contents)
if (nodeChar != null) {
required += AbstractTrie.requiredCapacity(keys, offset, length, index + 1); for (Map.Entry<String, V> entry : contents.entrySet())
{
return required; if (!put(entry.getKey(), entry.getValue()))
return false;
}
return true;
} }
} }

View File

@ -53,6 +53,7 @@ import java.util.Set;
* *
* @param <V> the Entry type * @param <V> the Entry type
*/ */
@Deprecated
class ArrayTernaryTrie<V> extends AbstractTrie<V> class ArrayTernaryTrie<V> extends AbstractTrie<V>
{ {
private static final int LO = 1; private static final int LO = 1;
@ -70,7 +71,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
* the 16 bit indexes can overflow and the trie * the 16 bit indexes can overflow and the trie
* cannot find existing entries anymore. * cannot find existing entries anymore.
*/ */
private static final int MAX_CAPACITY = 21_000; private static final int MAX_CAPACITY = Character.MAX_VALUE;
/** /**
* The Trie rows in a single array which allows a lookup of row,character * The Trie rows in a single array which allows a lookup of row,character
@ -99,18 +100,15 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
/** /**
* Create a Trie * Create a Trie
* *
* @param insensitive true if the Trie is insensitive to the case of the key. * @param caseSensitive true if the Trie is insensitive to the case of the key.
* @param capacity The capacity of the Trie, which is in the worst case * @param capacity The capacity of the Trie, which is in the worst case
* is the total number of characters of all keys stored in the Trie. * is the total number of characters of all keys stored in the Trie.
* The capacity needed is dependent of the shared prefixes of the keys. * @see AbstractTrie#requiredCapacity(Set, boolean)
* For example, a capacity of 6 nodes is required to store keys "foo"
* and "bar", but a capacity of only 4 is required to
* store "bar" and "bat".
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ArrayTernaryTrie(boolean insensitive, int capacity) ArrayTernaryTrie(boolean caseSensitive, int capacity)
{ {
super(insensitive); super(caseSensitive);
if (capacity > MAX_CAPACITY) if (capacity > MAX_CAPACITY)
throw new IllegalArgumentException("ArrayTernaryTrie maximum capacity overflow (" + capacity + " > " + MAX_CAPACITY + ")"); throw new IllegalArgumentException("ArrayTernaryTrie maximum capacity overflow (" + capacity + " > " + MAX_CAPACITY + ")");
_value = (V[])new Object[capacity]; _value = (V[])new Object[capacity];
@ -118,28 +116,6 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
_key = new String[capacity]; _key = new String[capacity];
} }
@SuppressWarnings("unchecked")
ArrayTernaryTrie(boolean insensitive, Map<String, V> initialValues)
{
super(insensitive);
// The calculated requiredCapacity does not take into account the
// extra reserved slot for the empty string key, nor the slots
// required for 'terminating' the entry (1 slot per key) so we
// have to add those.
Set<String> keys = initialValues.keySet();
int capacity = AbstractTrie.requiredCapacity(keys, !insensitive) + keys.size() + 1;
if (capacity > MAX_CAPACITY)
throw new IllegalArgumentException("ArrayTernaryTrie maximum capacity overflow (" + capacity + " > " + MAX_CAPACITY + ")");
_value = (V[])new Object[capacity];
_tree = new char[capacity * ROW_SIZE];
_key = new String[capacity];
for (Map.Entry<String, V> entry : initialValues.entrySet())
{
if (!put(entry.getKey(), entry.getValue()))
throw new AssertionError("Invalid capacity calculated (" + capacity + ") at '" + entry + "' for " + initialValues);
}
}
@Override @Override
public void clear() public void clear()
{ {
@ -158,8 +134,8 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
for (int k = 0; k < limit; k++) for (int k = 0; k < limit; k++)
{ {
char c = s.charAt(k); char c = s.charAt(k);
if (isCaseInsensitive() && c < 128) if (isCaseInsensitive())
c = StringUtil.lowercases[c]; c = StringUtil.asciiToLowerCase(c);
while (true) while (true)
{ {
@ -169,7 +145,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
if (t == _rows) if (t == _rows)
{ {
_rows++; _rows++;
if (_rows >= _key.length) if (_rows > _key.length)
{ {
_rows--; _rows--;
return false; return false;
@ -202,7 +178,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
if (t == _rows) if (t == _rows)
{ {
_rows++; _rows++;
if (_rows >= _key.length) if (_rows > _key.length)
{ {
_rows--; _rows--;
return false; return false;
@ -223,8 +199,8 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
for (int i = 0; i < len; ) for (int i = 0; i < len; )
{ {
char c = s.charAt(offset + i++); char c = s.charAt(offset + i++);
if (isCaseInsensitive() && c < 128) if (isCaseInsensitive())
c = StringUtil.lowercases[c]; c = StringUtil.asciiToLowerCase(c);
while (true) while (true)
{ {
@ -259,7 +235,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
{ {
byte c = (byte)(b.get(offset + i++) & 0x7f); byte c = (byte)(b.get(offset + i++) & 0x7f);
if (isCaseInsensitive()) if (isCaseInsensitive())
c = (byte)StringUtil.lowercases[c]; c = StringUtil.asciiToLowerCase(c);
while (true) while (true)
{ {
@ -305,8 +281,8 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
{ {
char c = s.charAt(offset++); char c = s.charAt(offset++);
len--; len--;
if (isCaseInsensitive() && c < 128) if (isCaseInsensitive())
c = StringUtil.lowercases[c]; c = StringUtil.asciiToLowerCase(c);
while (true) while (true)
{ {
@ -363,7 +339,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
byte c = (byte)(b[offset++] & 0x7f); byte c = (byte)(b[offset++] & 0x7f);
len--; len--;
if (isCaseInsensitive()) if (isCaseInsensitive())
c = (byte)StringUtil.lowercases[c]; c = StringUtil.asciiToLowerCase(c);
while (true) while (true)
{ {
@ -406,7 +382,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
{ {
byte c = (byte)(b.get(o + i) & 0x7f); byte c = (byte)(b.get(o + i) & 0x7f);
if (isCaseInsensitive()) if (isCaseInsensitive())
c = (byte)StringUtil.lowercases[c]; c = StringUtil.asciiToLowerCase(c);
while (true) while (true)
{ {
@ -443,20 +419,20 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
public String toString() public String toString()
{ {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
buf.append("ATT@").append(Integer.toHexString(hashCode())).append('{');
buf.append("ci=").append(isCaseInsensitive()).append(';');
buf.append("c=").append(_tree.length / ROW_SIZE).append(';');
for (int r = 0; r <= _rows; r++) for (int r = 0; r <= _rows; r++)
{ {
if (_key[r] != null && _value[r] != null) if (_key[r] != null && _value[r] != null)
{ {
buf.append(','); if (r != 0)
buf.append(',');
buf.append(_key[r]); buf.append(_key[r]);
buf.append('='); buf.append('=');
buf.append(_value[r].toString()); buf.append(String.valueOf(_value[r]));
} }
} }
if (buf.length() == 0)
return "{}";
buf.setCharAt(0, '{');
buf.append('}'); buf.append('}');
return buf.toString(); return buf.toString();
} }
@ -529,12 +505,13 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
} }
} }
static class Growing<V> extends AbstractTrie<V> @Deprecated
public static class Growing<V> extends AbstractTrie<V>
{ {
private final int _growby; private final int _growby;
private ArrayTernaryTrie<V> _trie; private ArrayTernaryTrie<V> _trie;
Growing(boolean insensitive, int capacity, int growby) public Growing(boolean insensitive, int capacity, int growby)
{ {
super(insensitive); super(insensitive);
_growby = growby; _growby = growby;

View File

@ -13,12 +13,12 @@
package org.eclipse.jetty.util; package org.eclipse.jetty.util;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/** /**
* <p>A Trie String lookup data structure using a fixed size array.</p> * <p>A Trie String lookup data structure using a fixed size array.</p>
@ -29,8 +29,8 @@ import java.util.Set;
* indexed in each lookup table, whilst infrequently used characters * indexed in each lookup table, whilst infrequently used characters
* must use a big character table. * must use a big character table.
* </p> * </p>
* <p>This Trie is very space efficient if the key characters are * <p>This Trie is space efficient if the key characters are
* from ' ', '+', '-', ':', ';', '.', 'A' to 'Z' or 'a' to 'z'. * from ' ', '+', '-', ':', ';', '.', '0' - '9', A' to 'Z' or 'a' to 'z'
* Other ISO-8859-1 characters can be used by the key, but less space * Other ISO-8859-1 characters can be used by the key, but less space
* efficiently. * efficiently.
* </p> * </p>
@ -45,221 +45,331 @@ import java.util.Set;
*/ */
class ArrayTrie<V> extends AbstractTrie<V> class ArrayTrie<V> extends AbstractTrie<V>
{ {
public static int MAX_CAPACITY = Character.MAX_VALUE;
/** /**
* The Size of a Trie row is how many characters can be looked * The Size of a Trie row is how many characters can be looked
* up directly without going to a big index. This is set at * up directly without going to a big index. This is set at
* 32 to cover case insensitive alphabet and a few other common * 32 to cover case insensitive alphabet and a few other common
* characters. * characters.
*/ */
private static final int ROW_SIZE = 32; private static final int ROW_SIZE = 48;
private static final int BIG_ROW_INSENSITIVE = 22;
private static final int BIG_ROW_SENSITIVE = 48;
private static final int X = Integer.MIN_VALUE;
/** /**
* The index lookup table, this maps a character as a byte * The index lookup table, this maps a character as a byte
* (ISO-8859-1 or UTF8) to an index within a Trie row * (ISO-8859-1 or UTF8) to a Trie index within a Trie row.
* Positive values are column indexes within the main {@link #_table}.
* Negative values are indexes within a {@link Node#_bigRow}.
* Values of {@link #X} are not indexed and must be searched for
* in the extended {@link Node#_bigRow}
*/ */
private static final int[] LOOKUP = private static final int[] LOOKUP_INSENSITIVE =
{ {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F // 0 1 2 3 4 5 6 7 8 9 A B C D E F
/*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*0*/ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
/*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*1*/ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
/*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, 30, -1, /*2*/ -1, -2, -3, -4, -5, -6, -7, -8, -9,-10,-11, 43, 44, 45, 46, 47,
/*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1, /*3*/ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 37, 38, 39, 40, 41, 42,
/*4*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /*4*/-12, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
/*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /*5*/ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,-13,-14,-15,-16, 36,
/*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /*6*/-17, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
/*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1 /*7*/ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,-18,-19,-20,-21, X,
}; };
/**
* The index lookup table, this maps a character as a byte
* (ISO-8859-1 or UTF8) to a Trie index within a Trie row.
* Positive values are column indexes within the main {@link #_table}.
* Negative values are indexes within a {@link Node#_bigRow}.
* Values of {@link #X} are not indexed and must be searched for
* in the extended {@link Node#_bigRow}
*/
private static final int[] LOOKUP_SENSITIVE =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
/*0*/ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
/*1*/ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
/*2*/ -1, -2, -3, -4, -5, -6, -7, -8, -9,-10,-11, 43, 44, 45, 46, 47,
/*3*/ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 37, 38, 39, 40, 41, 42,
/*4*/-12,-22,-23,-24,-25,-26,-27,-28,-29,-30,-31,-32,-33,-34,-35,-36,
/*5*/-37,-38,-39,-40,-41,-42,-43,-44,-45,-46,-47,-13,-14,-15,-16, 36,
/*6*/-17, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
/*7*/ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,-18,-19,-20,-21, X,
};
/**
* A Node in the tree.
* A Node instance is only needed for rows in the {@link #_table} array
* that either have a key/value pair or a {@link #_bigRow} extended row.
* @param <V> The value type of the node.
*/
private static class Node<V>
{
String _key;
V _value;
/**
* A big row of indexes in which extended characters can be found.
* The first {@link ArrayTrie#_bigRowSize} entries are accessed by negative
* indexes from the {@link ArrayTrie#_lookup} table. The following entries
* are character/row pairs that must be searched looking for a match.
* A big row is dynamically allocated to minimum size required for it's entries.
*/
char[] _bigRow;
@Override
public String toString()
{
return _key + "=" + _value;
}
}
/** /**
* The Trie rows in a single array which allows a lookup of row,character * The Trie rows in a single array which allows a lookup of row,character
* to the next row in the Trie. This is actually a 2 dimensional * to the next row in the Trie. This is actually a 2 dimensional
* array that has been flattened to achieve locality of reference. * array that has been flattened to achieve locality of reference.
* The first ROW_SIZE entries are for row 0, then next ROW_SIZE * The first ROW_SIZE entries are for row 0, then next ROW_SIZE
* entries are for row 1 etc. So in general instead of using * entries are for row 1 etc. So in general instead of using
* _rows[row][index], we use _rows[row*ROW_SIZE+index] to look up * _rows[row][column], we use _rows[row*ROW_SIZE+column] to look up
* the next row for a given character. * the next row for a given character.
* *
* The array is of characters rather than integers to save space. * The array is of characters rather than integers to save space.
*/ */
private final char[] _rowIndex; private final char[] _table;
private final int[] _lookup;
/** private final Node<V>[] _node;
* The key (if any) for a Trie row. private final int _bigRowSize;
* A row may be a leaf, a node or both in the Trie tree.
*/
private final String[] _key;
/**
* The value (if any) for a Trie row.
* A row may be a leaf, a node or both in the Trie tree.
*/
private final V[] _value;
/**
* A big index for each row.
* If a character outside of the lookup map is needed,
* then a big index will be created for the row, with
* 256 entries, one for each possible byte.
*/
private char[][] _bigIndex;
/**
* The number of rows allocated
*/
private char _rows; private char _rows;
/** Create a trie from capacity and content
* @param capacity The maximum capacity of the Trie or -1 for unlimited capacity
* @param caseSensitive True if the Trie keys are case sensitive
* @param contents The known contents of the Trie
* @param <V> The value type of the Trie
* @return a Trie containing the contents or null if not possible.
*/
public static <V> ArrayTrie<V> from(int capacity, boolean caseSensitive, Map<String, V> contents)
{
// can't do infinite capacity
if (capacity < 0)
return null;
if (capacity > MAX_CAPACITY)
return null;
ArrayTrie<V> trie = new ArrayTrie<>(caseSensitive, capacity);
if (contents != null && !trie.putAll(contents))
return null;
return trie;
}
/** /**
* @param capacity The capacity of the trie, which at the worst case * @param capacity The capacity of the trie, which at the worst case
* is the total number of characters of all keys stored in the Trie. * is the total number of characters of all keys stored in the Trie,
* The capacity needed is dependent of the shared prefixes of the keys. * plus 1 for the empty key.
* For example, a capacity of 6 nodes is required to store keys "foo" * @see AbstractTrie#requiredCapacity(Set, boolean)
* and "bar", but a capacity of only 4 is required to
* store "bar" and "bat".
*/ */
@SuppressWarnings("unchecked")
ArrayTrie(int capacity) ArrayTrie(int capacity)
{ {
super(true); this(false, capacity);
capacity++;
_value = (V[])new Object[capacity];
_rowIndex = new char[capacity * ROW_SIZE];
_key = new String[capacity];
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ArrayTrie(Map<String, V> initialValues) ArrayTrie(boolean caseSensitive, int capacity)
{ {
super(true); super(caseSensitive);
// The calculated requiredCapacity does not take into account the _bigRowSize = caseSensitive ? BIG_ROW_SENSITIVE : BIG_ROW_INSENSITIVE;
// extra reserved slot for the empty string key, so we have to add 1. if (capacity > MAX_CAPACITY)
int capacity = requiredCapacity(initialValues.keySet(), false) + 1; throw new IllegalArgumentException("Capacity " + capacity + " > " + MAX_CAPACITY);
_value = (V[])new Object[capacity]; _lookup = !caseSensitive ? LOOKUP_INSENSITIVE : LOOKUP_SENSITIVE;
_rowIndex = new char[capacity * ROW_SIZE]; _table = new char[capacity * ROW_SIZE];
_key = new String[capacity]; _node = new Node[capacity];
for (Map.Entry<String, V> entry : initialValues.entrySet())
{
if (!put(entry.getKey(), entry.getValue()))
throw new AssertionError("Invalid capacity calculated (" + capacity + ") at '" + entry + "' for " + initialValues);
}
} }
@Override @Override
public void clear() public void clear()
{ {
_rows = 0; _rows = 0;
Arrays.fill(_value, null); Arrays.fill(_table, (char)0);
Arrays.fill(_rowIndex, (char)0); Arrays.fill(_node, null);
Arrays.fill(_key, null);
} }
@Override @Override
public boolean put(String s, V v) public boolean put(String key, V value)
{ {
int t = 0; int row = 0;
int k; int limit = key.length();
int limit = s.length(); for (int i = 0; i < limit; i++)
for (k = 0; k < limit; k++)
{ {
char c = s.charAt(k); char c = key.charAt(i);
int column = c > 0x7f ? Integer.MIN_VALUE : _lookup[c];
int index = LOOKUP[c & 0x7f]; if (column >= 0)
if (index >= 0)
{ {
int idx = t * ROW_SIZE + index; // This character is indexed to a column of the main table
t = _rowIndex[idx]; int idx = row * ROW_SIZE + column;
if (t == 0) row = _table[idx];
if (row == 0)
{ {
if (++_rows >= _value.length) // not found so we need a new row
if (_rows == _node.length - 1)
return false; return false;
t = _rowIndex[idx] = _rows; row = _table[idx] = ++_rows;
}
}
else if (column != Integer.MIN_VALUE)
{
// This character is indexed to a column in the nodes bigRow
int idx = -column;
Node<V> node = _node[row];
if (node == null)
node = _node[row] = new Node<>();
char[] big = node._bigRow;
row = (big == null || idx >= big.length) ? 0 : big[idx];
if (row == 0)
{
// Not found, we need a new row
if (_rows == _node.length - 1)
return false;
// Expand the size of the bigRow to have +1 extended lookups
if (big == null)
big = node._bigRow = new char[idx + 1];
else if (idx >= big.length)
big = node._bigRow = Arrays.copyOf(big, idx + 1);
row = big[idx] = ++_rows;
} }
} }
else if (c > 127)
throw new IllegalArgumentException("non ascii character");
else else
{ {
if (_bigIndex == null) // This char is neither in the normal table, nor the first part of a bigRow
_bigIndex = new char[_value.length][]; // Look for it linearly in an extended big row.
if (t >= _bigIndex.length) int last = row;
return false; row = 0;
char[] big = _bigIndex[t]; Node<V> node = _node[last];
if (big == null) if (node != null)
big = _bigIndex[t] = new char[128];
t = big[c];
if (t == 0)
{ {
if (_rows == _value.length) char[] big = node._bigRow;
if (big != null)
{
for (int idx = _bigRowSize; idx < big.length; idx += 2)
{
if (big[idx] == c)
{
row = big[idx + 1];
break;
}
}
}
}
if (row == 0)
{
// Not found, so we need a new row
if (_rows == _node.length - 1)
return false; return false;
t = big[c] = ++_rows;
if (node == null)
node = _node[last] = new Node<>();
char[] big = node._bigRow;
// Expand the size of the bigRow to have extended lookups
if (big == null)
big = node._bigRow = new char[_bigRowSize + 2];
else
big = node._bigRow = Arrays.copyOf(big, Math.max(big.length, _bigRowSize) + 2);
// set the lookup char and its row
// TODO if the extended big row entries were sorted, then missed lookups could be aborted sooner
// TODO and/or a binary chop search could be done for hits.
big[big.length - 2] = c;
row = big[big.length - 1] = ++_rows;
} }
} }
} }
if (t >= _key.length) // We have processed all characters so set the key and value in the current Node
Node<V> node = _node[row];
if (node == null)
node = _node[row] = new Node<>();
node._key = key;
node._value = value;
return true;
}
private int lookup(int row, char c)
{
// If the char is small we can lookup in the index table
if (c < 0x80)
{ {
_rows = (char)_key.length; int column = _lookup[c];
return false; if (column != Integer.MIN_VALUE)
{
// The char is indexed, so should be in normal row or bigRow
if (column >= 0)
{
// look in the normal row
int idx = row * ROW_SIZE + column;
row = _table[idx];
}
else
{
// Look in the indexed part of the bigRow
Node<V> node = _node[row];
char[] big = node == null ? null : _node[row]._bigRow;
int idx = -column;
if (big == null || idx >= big.length)
return -1;
row = big[idx];
}
return row == 0 ? -1 : row;
}
} }
_key[t] = v == null ? null : s; // Not an indexed char, so do a linear search through he tail of the bigRow
_value[t] = v; Node<V> node = _node[row];
return true; char[] big = node == null ? null : node._bigRow;
if (big != null)
{
for (int i = _bigRowSize; i < big.length; i += 2)
if (big[i] == c)
return big[i + 1];
}
return -1;
} }
@Override @Override
public V get(String s, int offset, int len) public V get(String s, int offset, int len)
{ {
int t = 0; int row = 0;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
char c = s.charAt(offset + i); char c = s.charAt(offset + i);
int index = LOOKUP[c & 0x7f]; row = lookup(row, c);
if (index >= 0) if (row < 0)
{ return null;
int idx = t * ROW_SIZE + index;
t = _rowIndex[idx];
if (t == 0)
return null;
}
else
{
char[] big = _bigIndex == null ? null : _bigIndex[t];
if (big == null)
return null;
t = big[c];
if (t == 0)
return null;
}
} }
return _value[t]; Node<V> node = _node[row];
return node == null ? null : node._value;
} }
@Override @Override
public V get(ByteBuffer b, int offset, int len) public V get(ByteBuffer b, int offset, int len)
{ {
int t = 0; int row = 0;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
byte c = b.get(offset + i); byte c = b.get(offset + i);
int index = LOOKUP[c & 0x7f]; row = lookup(row, (char)(c & 0xff));
if (index >= 0) if (row < 0)
{ return null;
int idx = t * ROW_SIZE + index;
t = _rowIndex[idx];
if (t == 0)
return null;
}
else
{
char[] big = _bigIndex == null ? null : _bigIndex[t];
if (big == null)
return null;
t = big[c];
if (t == 0)
return null;
}
} }
return (V)_value[t]; Node<V> node = _node[row];
return node == null ? null : node._value;
} }
@Override @Override
@ -282,177 +392,108 @@ class ArrayTrie<V> extends AbstractTrie<V>
return getBest(0, s, offset, len); return getBest(0, s, offset, len);
} }
private V getBest(int t, String s, int offset, int len) private V getBest(int row, String s, int offset, int len)
{ {
int pos = offset; int pos = offset;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
char c = s.charAt(pos++); char c = s.charAt(pos++);
int index = LOOKUP[c & 0x7f]; int next = lookup(row, c);
if (index >= 0) if (next < 0)
{ break;
int idx = t * ROW_SIZE + index;
int nt = _rowIndex[idx];
if (nt == 0)
break;
t = nt;
}
else
{
char[] big = _bigIndex == null ? null : _bigIndex[t];
if (big == null)
return null;
int nt = big[c];
if (nt == 0)
break;
t = nt;
}
// Is the next Trie is a match // Is the row a match?
if (_key[t] != null) Node<V> node = _node[row];
if (node != null && node._key != null)
{ {
// Recurse so we can remember this possibility // Recurse so we can remember this possibility
V best = getBest(t, s, offset + i + 1, len - i - 1); V best = getBest(next, s, offset + i + 1, len - i - 1);
if (best != null) if (best != null)
return best; return best;
return (V)_value[t]; return node._value;
} }
row = next;
} }
return (V)_value[t]; Node<V> node = _node[row];
return node == null ? null : node._value;
} }
private V getBest(int t, byte[] b, int offset, int len) private V getBest(int row, byte[] b, int offset, int len)
{ {
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
byte c = b[offset + i]; byte c = b[offset + i];
int index = LOOKUP[c & 0x7f]; int next = lookup(row, (char)(c & 0xff));
if (index >= 0) if (next < 0)
{ break;
int idx = t * ROW_SIZE + index;
int nt = _rowIndex[idx];
if (nt == 0)
break;
t = nt;
}
else
{
char[] big = _bigIndex == null ? null : _bigIndex[t];
if (big == null)
return null;
int nt = big[c];
if (nt == 0)
break;
t = nt;
}
// Is the next Trie is a match // Is the next row a match?
if (_key[t] != null) Node<V> node = _node[row];
if (node != null && node._key != null)
{ {
// Recurse so we can remember this possibility // Recurse so we can remember this possibility
V best = getBest(t, b, offset + i + 1, len - i - 1); V best = getBest(next, b, offset + i + 1, len - i - 1);
if (best != null) if (best != null)
return best; return best;
break; return node._value;
} }
row = next;
} }
return (V)_value[t]; Node<V> node = _node[row];
return node == null ? null : node._value;
} }
private V getBest(int t, ByteBuffer b, int offset, int len) private V getBest(int row, ByteBuffer b, int offset, int len)
{ {
int pos = b.position() + offset; int pos = b.position() + offset;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
byte c = b.get(pos++); byte c = b.get(pos++);
int index = LOOKUP[c & 0x7f]; int next = lookup(row, (char)(c & 0xff));
if (index >= 0) if (next < 0)
{ break;
int idx = t * ROW_SIZE + index;
int nt = _rowIndex[idx];
if (nt == 0)
break;
t = nt;
}
else
{
char[] big = _bigIndex == null ? null : _bigIndex[t];
if (big == null)
return null;
int nt = big[c];
if (nt == 0)
break;
t = nt;
}
// Is the next Trie is a match // Is the next row a match?
if (_key[t] != null) Node<V> node = _node[row];
if (node != null && node._key != null)
{ {
// Recurse so we can remember this possibility // Recurse so we can remember this possibility
V best = getBest(t, b, offset + i + 1, len - i - 1); V best = getBest(next, b, offset + i + 1, len - i - 1);
if (best != null) if (best != null)
return best; return best;
break; return node._value;
} }
row = next;
} }
return (V)_value[t]; Node<V> node = _node[row];
return node == null ? null : node._value;
} }
@Override @Override
public String toString() public String toString()
{ {
StringBuilder buf = new StringBuilder(); return
toString(buf, 0); "AT@" + Integer.toHexString(hashCode()) + '{' +
"cs=" + isCaseSensitive() + ';' +
if (buf.length() == 0) "c=" + _table.length / ROW_SIZE + ';' +
return "{}"; Arrays.stream(_node)
.filter(n -> n != null && n._key != null)
buf.setCharAt(0, '{'); .map(Node::toString)
buf.append('}'); .collect(Collectors.joining(",")) +
return buf.toString(); '}';
}
private void toString(Appendable out, int t)
{
if (_value[t] != null)
{
try
{
out.append(',');
out.append(_key[t]);
out.append('=');
out.append(_value[t].toString());
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
for (int i = 0; i < ROW_SIZE; i++)
{
int idx = t * ROW_SIZE + i;
if (_rowIndex[idx] != 0)
toString(out, _rowIndex[idx]);
}
char[] big = _bigIndex == null ? null : _bigIndex[t];
if (big != null)
{
for (int i : big)
{
if (i != 0)
toString(out, i);
}
}
} }
@Override @Override
public Set<String> keySet() public Set<String> keySet()
{ {
Set<String> keys = new HashSet<>(); return Arrays.stream(_node)
keySet(keys, 0); .filter(Objects::nonNull)
return keys; .map(n -> n._key)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
} }
@Override @Override
@ -467,26 +508,72 @@ class ArrayTrie<V> extends AbstractTrie<V>
return keySet().isEmpty(); return keySet().isEmpty();
} }
private void keySet(Set<String> set, int t) public void dumpStdErr()
{ {
if (t < _value.length && _value[t] != null) System.err.print("row:");
set.add(_key[t]); for (int c = 0; c < ROW_SIZE; c++)
for (int i = 0; i < ROW_SIZE; i++)
{ {
int idx = t * ROW_SIZE + i; for (int i = 0; i < 0x7f; i++)
if (idx < _rowIndex.length && _rowIndex[idx] != 0)
keySet(set, _rowIndex[idx]);
}
char[] big = _bigIndex == null || t >= _bigIndex.length ? null : _bigIndex[t];
if (big != null)
{
for (int i : big)
{ {
if (i != 0) if (_lookup[i] == c)
keySet(set, i); {
System.err.printf(" %s", (char)i);
break;
}
} }
} }
System.err.println();
System.err.print("big:");
for (int c = 0; c < _bigRowSize; c++)
{
for (int i = 0; i < 0x7f; i++)
{
if (-_lookup[i] == c)
{
System.err.printf(" %s", (char)i);
break;
}
}
}
System.err.println();
for (int row = 0; row <= _rows; row++)
{
System.err.printf("%3x:", row);
for (int c = 0; c < ROW_SIZE; c++)
{
char ch = _table[row * ROW_SIZE + c];
if (ch == 0)
System.err.print(" .");
else
System.err.printf("%3x", (int)ch);
}
Node<V> node = _node[row];
if (node != null)
{
System.err.printf(" : %s%n", node);
char[] bigRow = node._bigRow;
if (bigRow != null)
{
System.err.print(" :");
for (int c = 0; c < Math.min(_bigRowSize, bigRow.length); c++)
{
char ch = bigRow[c];
if (ch == 0)
System.err.print(" _");
else
System.err.printf("%3x", (int)ch);
}
for (int c = _bigRowSize; c < bigRow.length; c += 2)
System.err.printf(" %s>%x", bigRow[c], (int)bigRow[c + 1]);
System.err.println();
}
}
else
System.err.println();
}
System.err.println();
} }
} }

View File

@ -15,18 +15,26 @@ package org.eclipse.jetty.util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
/** /**
* An empty trie implementation that never contains anything and never accepts new entries. * An empty trie implementation that never contains anything and never accepts new entries.
*
* @param <V> the entry type * @param <V> the entry type
*/ */
class EmptyTrie<V> extends AbstractTrie<V> class EmptyTrie<V> extends AbstractTrie<V>
{ {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private static final EmptyTrie SENSITIVE = new EmptyTrie<>(false); private static final EmptyTrie SENSITIVE = new EmptyTrie<>(true);
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private static final EmptyTrie INSENSITIVE = new EmptyTrie<>(true); private static final EmptyTrie INSENSITIVE = new EmptyTrie<>(false);
private EmptyTrie(boolean caseSensitive)
{
super(caseSensitive);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <V> EmptyTrie<V> instance(boolean caseSensitive) public static <V> EmptyTrie<V> instance(boolean caseSensitive)
@ -34,15 +42,21 @@ class EmptyTrie<V> extends AbstractTrie<V>
return caseSensitive ? SENSITIVE : INSENSITIVE; return caseSensitive ? SENSITIVE : INSENSITIVE;
} }
private EmptyTrie(boolean insensitive) @Override
public void clear()
{ {
super(insensitive);
} }
@Override @Override
public boolean put(String s, V v) public V get(String s)
{ {
return false; return null;
}
@Override
public V get(ByteBuffer b)
{
return null;
} }
@Override @Override
@ -57,6 +71,30 @@ class EmptyTrie<V> extends AbstractTrie<V>
return null; return null;
} }
@Override
public V getBest(String s)
{
return null;
}
@Override
public V getBest(byte[] b, int offset, int len)
{
return null;
}
@Override
public V getBest(ByteBuffer b)
{
return null;
}
@Override
public V getBest(byte[] b)
{
return null;
}
@Override @Override
public V getBest(String s, int offset, int len) public V getBest(String s, int offset, int len)
{ {
@ -81,6 +119,20 @@ class EmptyTrie<V> extends AbstractTrie<V>
return Collections.emptySet(); return Collections.emptySet();
} }
@Override
public boolean put(V v)
{
Objects.requireNonNull(v);
return false;
}
@Override
public boolean put(String s, V v)
{
Objects.requireNonNull(s);
return false;
}
@Override @Override
public int size() public int size()
{ {
@ -88,7 +140,8 @@ class EmptyTrie<V> extends AbstractTrie<V>
} }
@Override @Override
public void clear() protected boolean putAll(Map<String, V> contents)
{ {
return false;
} }
} }

View File

@ -35,7 +35,7 @@ public interface Index<V>
V get(String s); V get(String s);
/** /**
* Get an exact match from a segment of a ByteBuufer as key * Get an exact match from a segment of a ByteBuffer as key
* *
* @param b The buffer * @param b The buffer
* @return The value or null if not found * @return The value or null if not found
@ -53,7 +53,7 @@ public interface Index<V>
V get(String s, int offset, int len); V get(String s, int offset, int len);
/** /**
* Get an exact match from a segment of a ByteBuufer as key * Get an exact match from a segment of a ByteBuffer as key
* *
* @param b The buffer * @param b The buffer
* @param offset The offset within the buffer of the key * @param offset The offset within the buffer of the key
@ -72,6 +72,14 @@ public interface Index<V>
*/ */
V getBest(String s, int offset, int len); V getBest(String s, int offset, int len);
/**
* Get the best match from key in a String.
*
* @param s The string
* @return The value or null if not found
*/
V getBest(String s);
/** /**
* Get the best match from key in a byte buffer. * Get the best match from key in a byte buffer.
* The key is assumed to by ISO_8859_1 characters. * The key is assumed to by ISO_8859_1 characters.
@ -84,12 +92,16 @@ public interface Index<V>
V getBest(ByteBuffer b, int offset, int len); V getBest(ByteBuffer b, int offset, int len);
/** /**
* Get the best match from key in a String. * Get the best match from key in a byte buffer.
* The key is assumed to by ISO_8859_1 characters.
* *
* @param s The string * @param b The buffer
* @return The value or null if not found * @return The value or null if not found
*/ */
V getBest(String s); default V getBest(ByteBuffer b)
{
return getBest(b, 0, b.remaining());
}
/** /**
* Get the best match from key in a byte array. * Get the best match from key in a byte array.
@ -102,6 +114,18 @@ public interface Index<V>
*/ */
V getBest(byte[] b, int offset, int len); V getBest(byte[] b, int offset, int len);
/**
* Get the best match from key in a byte array.
* The key is assumed to by ISO_8859_1 characters.
*
* @param b The buffer
* @return The value or null if not found
*/
default V getBest(byte[] b)
{
return getBest(b, 0, b.length);
}
/** /**
* Check if the index contains any entry. * Check if the index contains any entry.
* *
@ -133,8 +157,8 @@ public interface Index<V>
/** /**
* Put an entry into the index. * Put an entry into the index.
* *
* @param s The key for the entry * @param s The key for the entry. Must be non null, but can be empty.
* @param v The value of the entry * @param v The value of the entry. Must be non null.
* @return True if the index had capacity to add the field. * @return True if the index had capacity to add the field.
*/ */
boolean put(String s, V v); boolean put(String s, V v);
@ -188,46 +212,61 @@ public interface Index<V>
return this; return this;
} }
/**
* Configure the index to be mutable.
*
* @return a {@link Mutable.Builder} configured like this builder.
*/
public Mutable.Builder<V> mutable()
{
return this;
}
/** /**
* Build a {@link Mutable} instance. * Build a {@link Mutable} instance.
* @return a {@link Mutable} instance. * @return a {@link Mutable} instance.
*/ */
public Mutable<V> build() public Mutable<V> build()
{ {
if (contents != null && maxCapacity == 0) if (maxCapacity == 0)
throw new IllegalStateException("Cannot create a mutable index with maxCapacity=0 and some contents"); return EmptyTrie.instance(caseSensitive);
// TODO we need to consider large size and alphabet when picking a trie impl // Work out needed capacity
Mutable<V> result; int capacity = (contents == null) ? 0 : AbstractTrie.requiredCapacity(contents.keySet(), caseSensitive);
if (maxCapacity > 0)
{
result = new ArrayTernaryTrie<>(!caseSensitive, maxCapacity);
}
else if (maxCapacity < 0)
{
if (caseSensitive)
result = new ArrayTernaryTrie.Growing<>(false, 512, 512);
else
result = new TreeTrie<>();
}
else
{
result = EmptyTrie.instance(caseSensitive);
}
if (contents != null) // check capacities
{ if (maxCapacity >= 0 && capacity > maxCapacity)
for (Map.Entry<String, V> entry : contents.entrySet()) throw new IllegalStateException("Insufficient maxCapacity for contents");
{
if (!result.put(entry.getKey(), entry.getValue())) // try all the tries
throw new AssertionError("Index capacity exceeded at " + entry.getKey()); AbstractTrie<V> trie = ArrayTrie.from(maxCapacity, caseSensitive, contents);
} if (trie != null)
} return trie;
return result; trie = TreeTrie.from(caseSensitive, contents);
if (trie != null)
return trie;
// Nothing suitable
throw new IllegalStateException("No suitable Trie implementation: " + this);
} }
} }
} }
/**
* A special purpose static builder for fast creation of specific Index type
* @param maxCapacity The max capacity of the index
* @param <V> The type of the index
* @return A case sensitive mutable Index tacking visible ASCII alphabet to a max capacity.
*/
static <V> Mutable<V> buildCaseSensitiveMutableVisibleAsciiAlphabet(int maxCapacity)
{
if (maxCapacity < 0 || maxCapacity > ArrayTrie.MAX_CAPACITY)
return new TreeTrie<>(true);
if (maxCapacity == 0)
return EmptyTrie.instance(true);
return new ArrayTrie<>(true, maxCapacity);
}
/** /**
* Builder of {@link Index} instances. * Builder of {@link Index} instances.
* @param <V> the entry type * @param <V> the entry type
@ -242,7 +281,8 @@ public interface Index<V>
*/ */
public Builder() public Builder()
{ {
this(false, null); this.caseSensitive = false;
this.contents = null;
} }
Builder(boolean caseSensitive, Map<String, V> contents) Builder(boolean caseSensitive, Map<String, V> contents)
@ -349,11 +389,22 @@ public interface Index<V>
if (contents == null) if (contents == null)
return EmptyTrie.instance(caseSensitive); return EmptyTrie.instance(caseSensitive);
// TODO we need to consider large size and alphabet when picking a trie impl int capacity = AbstractTrie.requiredCapacity(contents.keySet(), caseSensitive);
if (caseSensitive)
return new ArrayTernaryTrie<>(false, contents); AbstractTrie<V> trie = ArrayTrie.from(capacity, caseSensitive, contents);
else if (trie != null)
return new ArrayTrie<>(contents); return trie;
trie = TreeTrie.from(caseSensitive, contents);
if (trie != null)
return trie;
throw new IllegalStateException("No suitable Trie implementation : " + this);
}
@Override
public String toString()
{
return String.format("%s{c=%d,cs=%b}", super.toString(), contents == null ? 0 : contents.size(), caseSensitive);
} }
} }
} }

View File

@ -75,8 +75,7 @@ public class StringUtil
} }
// @checkstyle-disable-check : IllegalTokenTextCheck // @checkstyle-disable-check : IllegalTokenTextCheck
private static final char[] LOWERCASES =
public static final char[] lowercases =
{ {
'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007', '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017', '\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
@ -96,8 +95,73 @@ public class StringUtil
'\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177' '\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177'
}; };
// @checkstyle-disable-check : IllegalTokenTextCheck
private static final char[] UPPERCASES =
{
'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
'\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
'\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
'\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
'\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
'\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
'\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
'\100', '\101', '\102', '\103', '\104', '\105', '\106', '\107',
'\110', '\111', '\112', '\113', '\114', '\115', '\116', '\117',
'\120', '\121', '\122', '\123', '\124', '\125', '\126', '\127',
'\130', '\131', '\132', '\133', '\134', '\135', '\136', '\137',
'\140', '\101', '\102', '\103', '\104', '\105', '\106', '\107',
'\110', '\111', '\112', '\113', '\114', '\115', '\116', '\117',
'\120', '\121', '\122', '\123', '\124', '\125', '\126', '\127',
'\130', '\131', '\132', '\173', '\174', '\175', '\176', '\177'
};
// @checkstyle-enable-check : IllegalTokenTextCheck // @checkstyle-enable-check : IllegalTokenTextCheck
/**
* fast lower case conversion. Only works on ascii (not unicode)
*
* @param c the char to convert
* @return a lower case version of c
*/
public static char asciiToLowerCase(char c)
{
return (c < 0x80) ? LOWERCASES[c] : c;
}
/**
* fast lower case conversion. Only works on ascii (not unicode)
*
* @param c the byte to convert
* @return a lower case version of c
*/
public static byte asciiToLowerCase(byte c)
{
return (c > 0) ? (byte)LOWERCASES[c] : c;
}
/**
* fast upper case conversion. Only works on ascii (not unicode)
*
* @param c the char to convert
* @return a upper case version of c
*/
public static char asciiToUpperCase(char c)
{
return (c < 0x80) ? UPPERCASES[c] : c;
}
/**
* fast upper case conversion. Only works on ascii (not unicode)
*
* @param c the byte to convert
* @return a upper case version of c
*/
public static byte asciiToUpperCase(byte c)
{
return (c > 0) ? (byte)UPPERCASES[c] : c;
}
/** /**
* fast lower case conversion. Only works on ascii (not unicode) * fast lower case conversion. Only works on ascii (not unicode)
* *
@ -117,7 +181,7 @@ public class StringUtil
char c1 = s.charAt(i); char c1 = s.charAt(i);
if (c1 <= 127) if (c1 <= 127)
{ {
char c2 = lowercases[c1]; char c2 = LOWERCASES[c1];
if (c1 != c2) if (c1 != c2)
{ {
c = s.toCharArray(); c = s.toCharArray();
@ -129,12 +193,48 @@ public class StringUtil
while (i-- > 0) while (i-- > 0)
{ {
if (c[i] <= 127) if (c[i] <= 127)
c[i] = lowercases[c[i]]; c[i] = LOWERCASES[c[i]];
} }
return c == null ? s : new String(c); return c == null ? s : new String(c);
} }
/**
* fast upper case conversion. Only works on ascii (not unicode)
*
* @param s the string to convert
* @return a lower case version of s
*/
public static String asciiToUpperCase(String s)
{
if (s == null)
return null;
char[] c = null;
int i = s.length();
// look for first conversion
while (i-- > 0)
{
char c1 = s.charAt(i);
if (c1 <= 127)
{
char c2 = UPPERCASES[c1];
if (c1 != c2)
{
c = s.toCharArray();
c[i] = c2;
break;
}
}
}
while (i-- > 0)
{
if (c[i] <= 127)
c[i] = UPPERCASES[c[i]];
}
return c == null ? s : new String(c);
}
/** /**
* Replace all characters from input string that are known to have * Replace all characters from input string that are known to have
* special meaning in various filesystems. * special meaning in various filesystems.
@ -199,9 +299,9 @@ public class StringUtil
if (c1 != c2) if (c1 != c2)
{ {
if (c1 <= 127) if (c1 <= 127)
c1 = lowercases[c1]; c1 = LOWERCASES[c1];
if (c2 <= 127) if (c2 <= 127)
c2 = lowercases[c2]; c2 = LOWERCASES[c2];
if (c1 != c2) if (c1 != c2)
return false; return false;
} }
@ -229,9 +329,9 @@ public class StringUtil
if (c1 != c2) if (c1 != c2)
{ {
if (c1 <= 127) if (c1 <= 127)
c1 = lowercases[c1]; c1 = LOWERCASES[c1];
if (c2 <= 127) if (c2 <= 127)
c2 = lowercases[c2]; c2 = LOWERCASES[c2];
if (c1 != c2) if (c1 != c2)
return false; return false;
} }

View File

@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -39,9 +40,9 @@ import java.util.Set;
*/ */
class TreeTrie<V> extends AbstractTrie<V> class TreeTrie<V> extends AbstractTrie<V>
{ {
private static final int[] LOOKUP = private static final int[] LOOKUP_INSENSITIVE =
{ {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F // 0 1 2 3 4 5 6 7 8 9 A B C D E F
/*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, 30, -1, /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, 30, -1,
@ -51,57 +52,95 @@ class TreeTrie<V> extends AbstractTrie<V>
/*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
/*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1 /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1
}; };
private static final int[] LOOKUP_SENSITIVE =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
/*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, 30, -1,
/*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
/*4*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/*5*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
/*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1
};
private static final int INDEX = 32; private static final int INDEX = 32;
private final TreeTrie<V>[] _nextIndex;
private final List<TreeTrie<V>> _nextOther = new ArrayList<>();
private final char _c;
private String _key;
private V _value;
/** Create a trie from capacity and content
* @param caseSensitive True if the Trie keys are case sensitive
* @param contents The known contents of the Trie
* @param <V> The value type of the Trie
* @return a Trie containing the contents or null if not possible.
*/
public static <V> AbstractTrie<V> from(boolean caseSensitive, Map<String, V> contents)
{
TreeTrie<V> trie = new TreeTrie<>(caseSensitive);
if (contents != null && !trie.putAll(contents))
return null;
return trie;
}
private static class Node<V>
{
private final Node<V>[] _nextIndex;
private final List<Node<V>> _nextOther = new ArrayList<>();
private final char _c;
private String _key;
private V _value;
// TODO made this use a variable lookup row like ArrayTrie
@SuppressWarnings("unchecked")
private Node(char c)
{
_nextIndex = new Node[INDEX];
this._c = c;
}
}
private final int[] _lookup;
private final Node<V> _root;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
TreeTrie() TreeTrie()
{ {
super(true); this(false);
_nextIndex = new TreeTrie[INDEX];
_c = 0;
} }
@SuppressWarnings("unchecked") TreeTrie(boolean caseSensitive)
private TreeTrie(char c)
{ {
super(true); super(caseSensitive);
_nextIndex = new TreeTrie[INDEX]; _lookup = caseSensitive ? LOOKUP_SENSITIVE : LOOKUP_INSENSITIVE;
this._c = c; _root = new Node<V>((char)0);
} }
@Override @Override
public void clear() public void clear()
{ {
Arrays.fill(_nextIndex, null); Arrays.fill(_root._nextIndex, null);
_nextOther.clear(); _root._nextOther.clear();
_key = null; _root._key = null;
_value = null; _root._value = null;
} }
@Override @Override
public boolean put(String s, V v) public boolean put(String s, V v)
{ {
TreeTrie<V> t = this; Node<V> t = _root;
int limit = s.length(); int limit = s.length();
for (int k = 0; k < limit; k++) for (int k = 0; k < limit; k++)
{ {
char c = s.charAt(k); char c = s.charAt(k);
int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; int index = c < 0x7f ? _lookup[c] : -1;
if (index >= 0) if (index >= 0)
{ {
if (t._nextIndex[index] == null) if (t._nextIndex[index] == null)
t._nextIndex[index] = new TreeTrie<V>(c); t._nextIndex[index] = new Node<V>(c);
t = t._nextIndex[index]; t = t._nextIndex[index];
} }
else else
{ {
TreeTrie<V> n = null; Node<V> n = null;
for (int i = t._nextOther.size(); i-- > 0; ) for (int i = t._nextOther.size(); i-- > 0; )
{ {
n = t._nextOther.get(i); n = t._nextOther.get(i);
@ -111,7 +150,7 @@ class TreeTrie<V> extends AbstractTrie<V>
} }
if (n == null) if (n == null)
{ {
n = new TreeTrie<V>(c); n = new Node<V>(c);
t._nextOther.add(n); t._nextOther.add(n);
} }
t = n; t = n;
@ -125,11 +164,11 @@ class TreeTrie<V> extends AbstractTrie<V>
@Override @Override
public V get(String s, int offset, int len) public V get(String s, int offset, int len)
{ {
TreeTrie<V> t = this; Node<V> t = _root;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
char c = s.charAt(offset + i); char c = s.charAt(offset + i);
int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; int index = c < 0x7f ? _lookup[c] : -1;
if (index >= 0) if (index >= 0)
{ {
if (t._nextIndex[index] == null) if (t._nextIndex[index] == null)
@ -138,7 +177,7 @@ class TreeTrie<V> extends AbstractTrie<V>
} }
else else
{ {
TreeTrie<V> n = null; Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; ) for (int j = t._nextOther.size(); j-- > 0; )
{ {
n = t._nextOther.get(j); n = t._nextOther.get(j);
@ -157,11 +196,11 @@ class TreeTrie<V> extends AbstractTrie<V>
@Override @Override
public V get(ByteBuffer b, int offset, int len) public V get(ByteBuffer b, int offset, int len)
{ {
TreeTrie<V> t = this; Node<V> t = _root;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
byte c = b.get(offset + i); byte c = b.get(offset + i);
int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; int index = c >= 0 && c < 0x7f ? _lookup[c] : -1;
if (index >= 0) if (index >= 0)
{ {
if (t._nextIndex[index] == null) if (t._nextIndex[index] == null)
@ -170,7 +209,7 @@ class TreeTrie<V> extends AbstractTrie<V>
} }
else else
{ {
TreeTrie<V> n = null; Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; ) for (int j = t._nextOther.size(); j-- > 0; )
{ {
n = t._nextOther.get(j); n = t._nextOther.get(j);
@ -189,11 +228,15 @@ class TreeTrie<V> extends AbstractTrie<V>
@Override @Override
public V getBest(byte[] b, int offset, int len) public V getBest(byte[] b, int offset, int len)
{ {
TreeTrie<V> t = this; return getBest(_root, b, offset, len);
}
private V getBest(Node<V> t, byte[] b, int offset, int len)
{
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
byte c = b[offset + i]; byte c = b[offset + i];
int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; int index = c >= 0 && c < 0x7f ? _lookup[c] : -1;
if (index >= 0) if (index >= 0)
{ {
if (t._nextIndex[index] == null) if (t._nextIndex[index] == null)
@ -202,7 +245,7 @@ class TreeTrie<V> extends AbstractTrie<V>
} }
else else
{ {
TreeTrie<V> n = null; Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; ) for (int j = t._nextOther.size(); j-- > 0; )
{ {
n = t._nextOther.get(j); n = t._nextOther.get(j);
@ -219,7 +262,7 @@ class TreeTrie<V> extends AbstractTrie<V>
if (t._key != null) if (t._key != null)
{ {
// Recurse so we can remember this possibility // Recurse so we can remember this possibility
V best = t.getBest(b, offset + i + 1, len - i - 1); V best = getBest(t, b, offset + i + 1, len - i - 1);
if (best != null) if (best != null)
return best; return best;
break; break;
@ -243,11 +286,15 @@ class TreeTrie<V> extends AbstractTrie<V>
@Override @Override
public V getBest(String s, int offset, int len) public V getBest(String s, int offset, int len)
{ {
TreeTrie<V> t = this; return getBest(_root, s, offset, len);
}
private V getBest(Node<V> t, String s, int offset, int len)
{
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
byte c = (byte)(0xff & s.charAt(offset + i)); char c = s.charAt(offset + i);
int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; int index = c < 0x7f ? _lookup[c] : -1;
if (index >= 0) if (index >= 0)
{ {
if (t._nextIndex[index] == null) if (t._nextIndex[index] == null)
@ -256,7 +303,7 @@ class TreeTrie<V> extends AbstractTrie<V>
} }
else else
{ {
TreeTrie<V> n = null; Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; ) for (int j = t._nextOther.size(); j-- > 0; )
{ {
n = t._nextOther.get(j); n = t._nextOther.get(j);
@ -273,7 +320,7 @@ class TreeTrie<V> extends AbstractTrie<V>
if (t._key != null) if (t._key != null)
{ {
// Recurse so we can remember this possibility // Recurse so we can remember this possibility
V best = t.getBest(s, offset + i + 1, len - i - 1); V best = getBest(t, s, offset + i + 1, len - i - 1);
if (best != null) if (best != null)
return best; return best;
break; break;
@ -287,17 +334,16 @@ class TreeTrie<V> extends AbstractTrie<V>
{ {
if (b.hasArray()) if (b.hasArray())
return getBest(b.array(), b.arrayOffset() + b.position() + offset, len); return getBest(b.array(), b.arrayOffset() + b.position() + offset, len);
return getBestByteBuffer(b, offset, len); return getBest(_root, b, offset, len);
} }
private V getBestByteBuffer(ByteBuffer b, int offset, int len) private V getBest(Node<V> t, ByteBuffer b, int offset, int len)
{ {
TreeTrie<V> t = this;
int pos = b.position() + offset; int pos = b.position() + offset;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
byte c = b.get(pos++); byte c = b.get(pos++);
int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; int index = c >= 0 && c < 0x7f ? _lookup[c] : -1;
if (index >= 0) if (index >= 0)
{ {
if (t._nextIndex[index] == null) if (t._nextIndex[index] == null)
@ -306,7 +352,7 @@ class TreeTrie<V> extends AbstractTrie<V>
} }
else else
{ {
TreeTrie<V> n = null; Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; ) for (int j = t._nextOther.size(); j-- > 0; )
{ {
n = t._nextOther.get(j); n = t._nextOther.get(j);
@ -323,7 +369,7 @@ class TreeTrie<V> extends AbstractTrie<V>
if (t._key != null) if (t._key != null)
{ {
// Recurse so we can remember this possibility // Recurse so we can remember this possibility
V best = t.getBest(b, offset + i + 1, len - i - 1); V best = getBest(t, b, offset + i + 1, len - i - 1);
if (best != null) if (best != null)
return best; return best;
break; break;
@ -336,44 +382,63 @@ class TreeTrie<V> extends AbstractTrie<V>
public String toString() public String toString()
{ {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
toString(buf, this); buf.append("TT@").append(Integer.toHexString(hashCode())).append('{');
buf.append("ci=").append(isCaseInsensitive()).append(';');
if (buf.length() == 0) toString(buf, _root, "");
return "{}";
buf.setCharAt(0, '{');
buf.append('}'); buf.append('}');
return buf.toString(); return buf.toString();
} }
private static <V> void toString(Appendable out, TreeTrie<V> t) private static <V> void toString(Appendable out, Node<V> t, String separator)
{ {
if (t != null) loop: while (true)
{ {
if (t._value != null) if (t != null)
{ {
try if (t._value != null)
{ {
out.append(','); try
out.append(t._key); {
out.append('='); out.append(separator);
out.append(t._value.toString()); separator = ",";
out.append(t._key);
out.append('=');
out.append(t._value.toString());
}
catch (IOException e)
{
throw new RuntimeException(e);
}
} }
catch (IOException e)
for (int i = 0; i < INDEX;)
{ {
throw new RuntimeException(e); Node<V> n = t._nextIndex[i++];
if (n != null)
{
// can we avoid tail recurse?
if (i == INDEX && t._nextOther.size() == 0)
{
t = n;
continue loop;
}
// recurse
toString(out, n, separator);
}
}
for (int i = t._nextOther.size(); i-- > 0; )
{
// can we avoid tail recurse?
if (i == 0)
{
t = t._nextOther.get(i);
continue loop;
}
toString(out, t._nextOther.get(i), separator);
} }
} }
for (int i = 0; i < INDEX; i++) break;
{
if (t._nextIndex[i] != null)
toString(out, t._nextIndex[i]);
}
for (int i = t._nextOther.size(); i-- > 0; )
{
toString(out, t._nextOther.get(i));
}
} }
} }
@ -381,11 +446,11 @@ class TreeTrie<V> extends AbstractTrie<V>
public Set<String> keySet() public Set<String> keySet()
{ {
Set<String> keys = new HashSet<>(); Set<String> keys = new HashSet<>();
keySet(keys, this); keySet(keys, _root);
return keys; return keys;
} }
private static <V> void keySet(Set<String> set, TreeTrie<V> t) private static <V> void keySet(Set<String> set, Node<V> t)
{ {
if (t != null) if (t != null)
{ {

View File

@ -15,46 +15,38 @@ package org.eclipse.jetty.util;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.fail; import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class IndexTest public class IndexTest
{ {
@Test @Test
public void belowMaxCapacityTest() public void testImmutableTrieSelection()
{ {
int size = 10_450; // empty immutable index is always empty
assertThat(new Index.Builder<String>().build(), instanceOf(EmptyTrie.class));
Index.Builder<Integer> builder = new Index.Builder<>(); // index of ascii characters
builder.caseSensitive(true); assertThat(new Index.Builder<String>().caseSensitive(false).with("name", "value").build(), instanceOf(ArrayTrie.class));
for (int i = 0; i < size; i++) assertThat(new Index.Builder<String>().caseSensitive(true).with("name", "value").build(), instanceOf(ArrayTrie.class));
{
builder.with("/test/group" + i, i);
}
Index<Integer> index = builder.build();
for (int i = 0; i < size; i++) // large index
{ String hugekey = "x".repeat(Character.MAX_VALUE + 1);
Integer integer = index.get("/test/group" + i); assertTrue(new Index.Builder<String>().caseSensitive(false).with(hugekey, "value").build() instanceof TreeTrie);
if (integer == null) assertTrue(new Index.Builder<String>().caseSensitive(true).with(hugekey, "value").build() instanceof TreeTrie);
fail("missing entry for '/test/group" + i + "'");
else if (integer != i)
fail("incorrect value for '/test/group" + i + "' (" + integer + ")");
}
} }
@Test @Test
public void overMaxCapacityTest() public void testUnlimitdMutableTrieSelection()
{ {
int size = 11_000; assertThat(new Index.Builder<String>().mutable().build(), instanceOf(TreeTrie.class));
}
Index.Builder<Integer> builder = new Index.Builder<>(); @Test
builder.caseSensitive(true); public void testLimitedMutableTrieSelection()
for (int i = 0; i < size; i++) {
{ assertThat(new Index.Builder<String>().mutable().maxCapacity(500).build(), instanceOf(ArrayTrie.class));
builder.with("/test/group" + i, i); assertThat(new Index.Builder<String>().mutable().maxCapacity(Character.MAX_VALUE + 1).build(), instanceOf(TreeTrie.class));
}
assertThrows(IllegalArgumentException.class, builder::build);
} }
} }

View File

@ -13,12 +13,13 @@
package org.eclipse.jetty.util; package org.eclipse.jetty.util;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
@ -26,32 +27,84 @@ import org.junit.jupiter.params.provider.MethodSource;
import static org.eclipse.jetty.util.AbstractTrie.requiredCapacity; import static org.eclipse.jetty.util.AbstractTrie.requiredCapacity;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.in;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
public class TrieTest public class TrieTest
{ {
private static final String[] KEYS =
{
"hello",
"helloHello",
"He",
"HELL",
"wibble",
"Wobble",
"foo-bar",
"foo+bar",
"HELL4"
};
private static final String[] X_KEYS = Arrays.stream(KEYS).map(s -> "%" + s + "%").toArray(String[]::new);
private static final String[] NOT_KEYS =
{
"h",
"helloHell",
"helloHelloHELLO",
"wibble0",
"foo_bar",
"foo-bar-bob",
"HELL5",
"\u0000"
};
private static final String[] BEST_NOT_KEYS =
{
null,
"hello",
"helloHello",
"wibble",
null,
"foo-bar",
"HELL",
null,
};
private static final String[] BEST_NOT_KEYS_LOWER =
{
null,
"hello",
"hello",
"wibble",
null,
"foo-bar",
null,
null,
};
private static final String[] X_NOT_KEYS = Arrays.stream(NOT_KEYS).map(s -> "%" + s + "%%%%").toArray(String[]::new);
public static Stream<Arguments> implementations() public static Stream<Arguments> implementations()
{ {
List<AbstractTrie<Integer>> impls = new ArrayList<>(); List<AbstractTrie<Integer>> impls = new ArrayList<>();
impls.add(new ArrayTrie<Integer>(128)); for (boolean caseSensitive : new boolean[] {true, false})
impls.add(new ArrayTernaryTrie<Integer>(true, 128)); {
impls.add(new ArrayTernaryTrie.Growing<Integer>(true, 128, 128)); impls.add(new ArrayTrie<Integer>(caseSensitive,128));
impls.add(new ArrayTernaryTrie<Integer>(caseSensitive, 128));
impls.add(new TreeTrie<>(caseSensitive));
}
for (AbstractTrie<Integer> trie : impls) for (AbstractTrie<Integer> trie : impls)
{ {
trie.put("hello", 1); for (int i = 0; i < KEYS.length; i++)
trie.put("He", 2); {
trie.put("HELL", 3); if (!trie.put(KEYS[i], i))
trie.put("wibble", 4); throw new IllegalStateException();
trie.put("Wobble", 5); }
trie.put("foo-bar", 6);
trie.put("foo+bar", 7);
trie.put("HELL4", 8);
trie.put("", 9);
} }
return impls.stream().map(Arguments::of); return impls.stream().map(Arguments::of);
@ -61,159 +114,174 @@ public class TrieTest
@MethodSource("implementations") @MethodSource("implementations")
public void testKeySet(AbstractTrie<Integer> trie) throws Exception public void testKeySet(AbstractTrie<Integer> trie) throws Exception
{ {
String[] values = new String[]{ for (String value : KEYS)
"hello", assertThat(value, trie.keySet().contains(value), is(true));
"He", for (String value : NOT_KEYS)
"HELL", assertThat(value, trie.keySet().contains(value), is(false));
"wibble",
"Wobble",
"foo-bar",
"foo+bar",
"HELL4",
""
};
for (String value : values)
{
assertThat(value, is(in(trie.keySet())));
}
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetString(AbstractTrie<Integer> trie) throws Exception public void testGetString(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.get("hello").intValue()); for (int i = 0; i < KEYS.length; i++)
assertEquals(2, trie.get("He").intValue()); assertThat(Integer.toString(i), trie.get(KEYS[i]), is(i));
assertEquals(3, trie.get("HELL").intValue()); for (int i = 0; i < NOT_KEYS.length; i++)
assertEquals(4, trie.get("wibble").intValue()); assertThat(Integer.toString(i), trie.get(NOT_KEYS[i]), nullValue());
assertEquals(5, trie.get("Wobble").intValue()); for (int i = 0; i < KEYS.length; i++)
assertEquals(6, trie.get("foo-bar").intValue()); {
assertEquals(7, trie.get("foo+bar").intValue()); String k = KEYS[i].toLowerCase();
Integer actual = trie.get(k);
assertEquals(1, trie.get("Hello").intValue()); if (k.equals(KEYS[i]) || trie.isCaseInsensitive())
assertEquals(2, trie.get("HE").intValue()); assertThat(k, actual, is(i));
assertEquals(3, trie.get("heLL").intValue()); else
assertEquals(4, trie.get("Wibble").intValue()); assertThat(k, actual, nullValue());
assertEquals(5, trie.get("wobble").intValue()); }
assertEquals(6, trie.get("Foo-bar").intValue()); for (int i = 0; i < KEYS.length; i++)
assertEquals(7, trie.get("FOO+bar").intValue()); {
assertEquals(8, trie.get("HELL4").intValue()); String k = KEYS[i].toUpperCase();
assertEquals(9, trie.get("").intValue()); Integer actual = trie.get(k);
if (k.equals(KEYS[i]) || trie.isCaseInsensitive())
assertEquals(null, trie.get("helloworld")); assertThat(k, actual, is(i));
assertEquals(null, trie.get("Help")); else
assertEquals(null, trie.get("Blah")); assertThat(k, actual, nullValue());
}
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetBuffer(AbstractTrie<Integer> trie) throws Exception public void testGetBuffer(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.get(BufferUtil.toBuffer("xhellox"), 1, 5).intValue()); for (int i = 0; i < KEYS.length; i++)
assertEquals(2, trie.get(BufferUtil.toBuffer("xhellox"), 1, 2).intValue()); assertThat(Integer.toString(i), trie.get(BufferUtil.toBuffer(X_KEYS[i]), 1, KEYS[i].length()), is(i));
assertEquals(3, trie.get(BufferUtil.toBuffer("xhellox"), 1, 4).intValue()); for (int i = 0; i < NOT_KEYS.length; i++)
assertEquals(4, trie.get(BufferUtil.toBuffer("wibble"), 0, 6).intValue()); assertThat(Integer.toString(i), trie.get(BufferUtil.toBuffer(X_NOT_KEYS[i]), 1, NOT_KEYS[i].length()), nullValue());
assertEquals(5, trie.get(BufferUtil.toBuffer("xWobble"), 1, 6).intValue()); for (int i = 0; i < KEYS.length; i++)
assertEquals(6, trie.get(BufferUtil.toBuffer("xfoo-barx"), 1, 7).intValue()); {
assertEquals(7, trie.get(BufferUtil.toBuffer("xfoo+barx"), 1, 7).intValue()); String k = X_KEYS[i].toLowerCase();
Integer actual = trie.get(BufferUtil.toBuffer(k), 1, KEYS[i].length());
assertEquals(1, trie.get(BufferUtil.toBuffer("xhellox"), 1, 5).intValue()); if (k.equals(X_KEYS[i]) || trie.isCaseInsensitive())
assertEquals(2, trie.get(BufferUtil.toBuffer("xHELLox"), 1, 2).intValue()); assertThat(k, actual, is(i));
assertEquals(3, trie.get(BufferUtil.toBuffer("xhellox"), 1, 4).intValue()); else
assertEquals(4, trie.get(BufferUtil.toBuffer("Wibble"), 0, 6).intValue()); assertThat(k, actual, nullValue());
assertEquals(5, trie.get(BufferUtil.toBuffer("xwobble"), 1, 6).intValue()); }
assertEquals(6, trie.get(BufferUtil.toBuffer("xFOO-barx"), 1, 7).intValue()); for (int i = 0; i < KEYS.length; i++)
assertEquals(7, trie.get(BufferUtil.toBuffer("xFOO+barx"), 1, 7).intValue()); {
String k = X_KEYS[i].toUpperCase();
assertEquals(null, trie.get(BufferUtil.toBuffer("xHelloworldx"), 1, 10)); Integer actual = trie.get(BufferUtil.toBuffer(k), 1, KEYS[i].length());
assertEquals(null, trie.get(BufferUtil.toBuffer("xHelpx"), 1, 4)); if (k.equals(X_KEYS[i]) || trie.isCaseInsensitive())
assertEquals(null, trie.get(BufferUtil.toBuffer("xBlahx"), 1, 4)); assertThat(k, actual, is(i));
else
assertThat(k, actual, nullValue());
}
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetDirectBuffer(AbstractTrie<Integer> trie) throws Exception public void testGetDirectBuffer(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 5).intValue()); for (int i = 0; i < KEYS.length; i++)
assertEquals(2, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 2).intValue()); assertThat(Integer.toString(i), trie.get(BufferUtil.toDirectBuffer(X_KEYS[i]), 1, KEYS[i].length()), is(i));
assertEquals(3, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 4).intValue()); for (int i = 0; i < NOT_KEYS.length; i++)
assertEquals(4, trie.get(BufferUtil.toDirectBuffer("wibble"), 0, 6).intValue()); assertThat(Integer.toString(i), trie.get(BufferUtil.toDirectBuffer(X_NOT_KEYS[i]), 1, NOT_KEYS[i].length()), nullValue());
assertEquals(5, trie.get(BufferUtil.toDirectBuffer("xWobble"), 1, 6).intValue()); for (int i = 0; i < KEYS.length; i++)
assertEquals(6, trie.get(BufferUtil.toDirectBuffer("xfoo-barx"), 1, 7).intValue()); {
assertEquals(7, trie.get(BufferUtil.toDirectBuffer("xfoo+barx"), 1, 7).intValue()); String k = X_KEYS[i].toLowerCase();
Integer actual = trie.get(BufferUtil.toDirectBuffer(k), 1, KEYS[i].length());
assertEquals(1, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 5).intValue()); if (k.equals(X_KEYS[i]) || trie.isCaseInsensitive())
assertEquals(2, trie.get(BufferUtil.toDirectBuffer("xHELLox"), 1, 2).intValue()); assertThat(k, actual, is(i));
assertEquals(3, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 4).intValue()); else
assertEquals(4, trie.get(BufferUtil.toDirectBuffer("Wibble"), 0, 6).intValue()); assertThat(k, actual, nullValue());
assertEquals(5, trie.get(BufferUtil.toDirectBuffer("xwobble"), 1, 6).intValue()); }
assertEquals(6, trie.get(BufferUtil.toDirectBuffer("xFOO-barx"), 1, 7).intValue()); for (int i = 0; i < KEYS.length; i++)
assertEquals(7, trie.get(BufferUtil.toDirectBuffer("xFOO+barx"), 1, 7).intValue()); {
String k = X_KEYS[i].toUpperCase();
assertEquals(null, trie.get(BufferUtil.toDirectBuffer("xHelloworldx"), 1, 10)); Integer actual = trie.get(BufferUtil.toDirectBuffer(k), 1, KEYS[i].length());
assertEquals(null, trie.get(BufferUtil.toDirectBuffer("xHelpx"), 1, 4)); if (k.equals(X_KEYS[i]) || trie.isCaseInsensitive())
assertEquals(null, trie.get(BufferUtil.toDirectBuffer("xBlahx"), 1, 4)); assertThat(k, actual, is(i));
else
assertThat(k, actual, nullValue());
}
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetBestArray(AbstractTrie<Integer> trie) throws Exception public void testGetBestArray(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.getBest(StringUtil.getUtf8Bytes("xhelloxxxx"), 1, 8).intValue()); for (int i = 0; i < NOT_KEYS.length; i++)
assertEquals(2, trie.getBest(StringUtil.getUtf8Bytes("xhelxoxxxx"), 1, 8).intValue()); {
assertEquals(3, trie.getBest(StringUtil.getUtf8Bytes("xhellxxxxx"), 1, 8).intValue()); String k = X_NOT_KEYS[i];
assertEquals(6, trie.getBest(StringUtil.getUtf8Bytes("xfoo-barxx"), 1, 8).intValue()); Integer actual = trie.getBest(StringUtil.getUtf8Bytes(k), 1, X_NOT_KEYS[i].length() - 1);
assertEquals(8, trie.getBest(StringUtil.getUtf8Bytes("xhell4xxxx"), 1, 8).intValue()); Integer expected = BEST_NOT_KEYS[i] == null ? null : trie.get(BEST_NOT_KEYS[i]);
assertThat(k, actual, is(expected));
}
assertEquals(1, trie.getBest(StringUtil.getUtf8Bytes("xHELLOxxxx"), 1, 8).intValue()); for (int i = 0; i < NOT_KEYS.length; i++)
assertEquals(2, trie.getBest(StringUtil.getUtf8Bytes("xHELxoxxxx"), 1, 8).intValue()); {
assertEquals(3, trie.getBest(StringUtil.getUtf8Bytes("xHELLxxxxx"), 1, 8).intValue()); String k = X_NOT_KEYS[i].toLowerCase();
assertEquals(6, trie.getBest(StringUtil.getUtf8Bytes("xfoo-BARxx"), 1, 8).intValue()); Integer actual = trie.getBest(StringUtil.getUtf8Bytes(k), 1, X_NOT_KEYS[i].length() - 1);
assertEquals(8, trie.getBest(StringUtil.getUtf8Bytes("xHELL4xxxx"), 1, 8).intValue()); String[] expectations = trie.isCaseSensitive() ? BEST_NOT_KEYS_LOWER : BEST_NOT_KEYS;
assertEquals(9, trie.getBest(StringUtil.getUtf8Bytes("xZZZZZxxxx"), 1, 8).intValue()); Integer expected = expectations[i] == null ? null : trie.get(expectations[i]);
assertThat(k, actual, is(expected));
}
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetBestBuffer(AbstractTrie<Integer> trie) throws Exception public void testGetBestBuffer(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.getBest(BufferUtil.toBuffer("xhelloxxxx"), 1, 8).intValue()); for (int i = 0; i < NOT_KEYS.length; i++)
assertEquals(2, trie.getBest(BufferUtil.toBuffer("xhelxoxxxx"), 1, 8).intValue()); {
assertEquals(3, trie.getBest(BufferUtil.toBuffer("xhellxxxxx"), 1, 8).intValue()); String k = X_NOT_KEYS[i];
assertEquals(6, trie.getBest(BufferUtil.toBuffer("xfoo-barxx"), 1, 8).intValue()); Integer actual = trie.getBest(BufferUtil.toBuffer(k), 1, X_NOT_KEYS[i].length() - 1);
assertEquals(8, trie.getBest(BufferUtil.toBuffer("xhell4xxxx"), 1, 8).intValue()); Integer expected = BEST_NOT_KEYS[i] == null ? null : trie.get(BEST_NOT_KEYS[i]);
assertThat(k, actual, is(expected));
}
assertEquals(1, trie.getBest(BufferUtil.toBuffer("xHELLOxxxx"), 1, 8).intValue()); for (int i = 0; i < NOT_KEYS.length; i++)
assertEquals(2, trie.getBest(BufferUtil.toBuffer("xHELxoxxxx"), 1, 8).intValue()); {
assertEquals(3, trie.getBest(BufferUtil.toBuffer("xHELLxxxxx"), 1, 8).intValue()); String k = X_NOT_KEYS[i].toLowerCase();
assertEquals(6, trie.getBest(BufferUtil.toBuffer("xfoo-BARxx"), 1, 8).intValue()); Integer actual = trie.getBest(BufferUtil.toBuffer(k), 1, X_NOT_KEYS[i].length() - 1);
assertEquals(8, trie.getBest(BufferUtil.toBuffer("xHELL4xxxx"), 1, 8).intValue()); String[] expectations = trie.isCaseSensitive() ? BEST_NOT_KEYS_LOWER : BEST_NOT_KEYS;
assertEquals(9, trie.getBest(BufferUtil.toBuffer("xZZZZZxxxx"), 1, 8).intValue()); Integer expected = expectations[i] == null ? null : trie.get(expectations[i]);
assertThat(k, actual, is(expected));
ByteBuffer buffer = (ByteBuffer)BufferUtil.toBuffer("xhelloxxxxxxx").position(2); }
assertEquals(1, trie.getBest(buffer, -1, 10).intValue());
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetBestDirectBuffer(AbstractTrie<Integer> trie) throws Exception public void testGetBestDirectBuffer(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.getBest(BufferUtil.toDirectBuffer("xhelloxxxx"), 1, 8).intValue()); for (int i = 0; i < NOT_KEYS.length; i++)
assertEquals(2, trie.getBest(BufferUtil.toDirectBuffer("xhelxoxxxx"), 1, 8).intValue()); {
assertEquals(3, trie.getBest(BufferUtil.toDirectBuffer("xhellxxxxx"), 1, 8).intValue()); String k = X_NOT_KEYS[i];
assertEquals(6, trie.getBest(BufferUtil.toDirectBuffer("xfoo-barxx"), 1, 8).intValue()); Integer actual = trie.getBest(BufferUtil.toDirectBuffer(k), 1, X_NOT_KEYS[i].length() - 1);
assertEquals(8, trie.getBest(BufferUtil.toDirectBuffer("xhell4xxxx"), 1, 8).intValue()); Integer expected = BEST_NOT_KEYS[i] == null ? null : trie.get(BEST_NOT_KEYS[i]);
assertThat(k, actual, is(expected));
}
assertEquals(1, trie.getBest(BufferUtil.toDirectBuffer("xHELLOxxxx"), 1, 8).intValue()); for (int i = 0; i < NOT_KEYS.length; i++)
assertEquals(2, trie.getBest(BufferUtil.toDirectBuffer("xHELxoxxxx"), 1, 8).intValue()); {
assertEquals(3, trie.getBest(BufferUtil.toDirectBuffer("xHELLxxxxx"), 1, 8).intValue()); String k = X_NOT_KEYS[i].toLowerCase();
assertEquals(6, trie.getBest(BufferUtil.toDirectBuffer("xfoo-BARxx"), 1, 8).intValue()); Integer actual = trie.getBest(BufferUtil.toDirectBuffer(k), 1, X_NOT_KEYS[i].length() - 1);
assertEquals(8, trie.getBest(BufferUtil.toDirectBuffer("xHELL4xxxx"), 1, 8).intValue()); String[] expectations = trie.isCaseSensitive() ? BEST_NOT_KEYS_LOWER : BEST_NOT_KEYS;
assertEquals(9, trie.getBest(BufferUtil.toDirectBuffer("xZZZZZxxxx"), 1, 8).intValue()); Integer expected = expectations[i] == null ? null : trie.get(expectations[i]);
assertThat(k, actual, is(expected));
}
}
ByteBuffer buffer = (ByteBuffer)BufferUtil.toDirectBuffer("xhelloxxxxxxx").position(2); @ParameterizedTest
assertEquals(1, trie.getBest(buffer, -1, 10).intValue()); @MethodSource("implementations")
public void testOtherChars(AbstractTrie<Integer> trie) throws Exception
{
Assumptions.assumeTrue(trie instanceof ArrayTrie<?> || trie instanceof TreeTrie);
assertTrue(trie.put("8859:ä", -1));
assertTrue(trie.put("inv:\r\n", -2));
assertTrue(trie.put("utf:\u20ac", -3));
assertThat(trie.getBest("8859:äxxxxx"), is(-1));
assertThat(trie.getBest("inv:\r\n:xxxx"), is(-2));
assertThat(trie.getBest("utf:\u20ac"), is(-3));
} }
@ParameterizedTest @ParameterizedTest
@ -232,29 +300,98 @@ public class TrieTest
@Test @Test
public void testRequiredCapacity() public void testRequiredCapacity()
{ {
assertThat(requiredCapacity(Set.of("ABC", "abc"), true), is(6)); assertThat(requiredCapacity(Set.of("ABC", "abc"), true), is(1 + 6));
assertThat(requiredCapacity(Set.of("ABC", "abc"), false), is(3)); assertThat(requiredCapacity(Set.of("ABC", "abc"), false), is(1 + 3));
assertThat(requiredCapacity(Set.of(""), false), is(0)); assertThat(requiredCapacity(Set.of(""), false), is(1 + 0));
assertThat(requiredCapacity(Set.of("ABC", ""), false), is(3)); assertThat(requiredCapacity(Set.of("ABC", ""), false), is(1 + 3));
assertThat(requiredCapacity(Set.of("ABC"), false), is(3)); assertThat(requiredCapacity(Set.of("ABC"), false), is(1 + 3));
assertThat(requiredCapacity(Set.of("ABC", "XYZ"), false), is(6)); assertThat(requiredCapacity(Set.of("ABC", "XYZ"), false), is(1 + 6));
assertThat(requiredCapacity(Set.of("A00", "A11"), false), is(5)); assertThat(requiredCapacity(Set.of("A00", "A11"), false), is(1 + 5));
assertThat(requiredCapacity(Set.of("A00", "A01", "A10", "A11"), false), is(7)); assertThat(requiredCapacity(Set.of("A00", "A01", "A10", "A11"), false), is(1 + 7));
assertThat(requiredCapacity(Set.of("A", "AB"), false), is(2)); assertThat(requiredCapacity(Set.of("A", "AB"), false), is(1 + 2));
assertThat(requiredCapacity(Set.of("A", "ABC"), false), is(3)); assertThat(requiredCapacity(Set.of("A", "ABC"), false), is(1 + 3));
assertThat(requiredCapacity(Set.of("A", "ABCD"), false), is(4)); assertThat(requiredCapacity(Set.of("A", "ABCD"), false), is(1 + 4));
assertThat(requiredCapacity(Set.of("AB", "ABC"), false), is(3)); assertThat(requiredCapacity(Set.of("AB", "ABC"), false), is(1 + 3));
assertThat(requiredCapacity(Set.of("ABC", "ABCD"), false), is(4)); assertThat(requiredCapacity(Set.of("ABC", "ABCD"), false), is(1 + 4));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(6)); assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(1 + 6));
assertThat(requiredCapacity(Set.of("AB", "A"), false), is(2)); assertThat(requiredCapacity(Set.of("AB", "A"), false), is(1 + 2));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(6)); assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(1 + 6));
assertThat(requiredCapacity(Set.of("ABCDEF", "ABC"), false), is(6)); assertThat(requiredCapacity(Set.of("ABCDEF", "ABC"), false), is(1 + 6));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF", "ABX"), false), is(7)); assertThat(requiredCapacity(Set.of("ABC", "ABCDEF", "ABX"), false), is(1 + 7));
assertThat(requiredCapacity(Set.of("ABCDEF", "ABC", "ABX"), false), is(7)); assertThat(requiredCapacity(Set.of("ABCDEF", "ABC", "ABX"), false), is(1 + 7));
assertThat(requiredCapacity(Set.of("ADEF", "AQPR4", "AQZ"), false), is(9)); assertThat(requiredCapacity(Set.of("ADEF", "AQPR4", "AQZ"), false), is(1 + 9));
assertThat(requiredCapacity(Set.of("111", "ADEF", "AQPR4", "AQZ", "999"), false), is(15)); assertThat(requiredCapacity(Set.of("111", "ADEF", "AQPR4", "AQZ", "999"), false), is(1 + 15));
assertThat(requiredCapacity(Set.of("utf-16", "utf-8"), false), is(7)); assertThat(requiredCapacity(Set.of("utf-16", "utf-8"), false), is(1 + 7));
assertThat(requiredCapacity(Set.of("utf-16", "utf-8", "utf16", "utf8"), false), is(10)); assertThat(requiredCapacity(Set.of("utf-16", "utf-8", "utf16", "utf8"), false), is(1 + 10));
assertThat(requiredCapacity(Set.of("utf-8", "utf8", "utf-16", "utf16", "iso-8859-1", "iso_8859_1"), false), is(27)); assertThat(requiredCapacity(Set.of("utf-8", "utf8", "utf-16", "utf16", "iso-8859-1", "iso_8859_1"), false), is(1 + 27));
}
@Test
public void testLargeRequiredCapacity()
{
String x = "x".repeat(Character.MAX_VALUE / 2);
String y = "y".repeat(Character.MAX_VALUE / 2);
String z = "z".repeat(Character.MAX_VALUE / 2);
assertThat(requiredCapacity(Set.of(x, y, z), true), is(1 + 3 * (Character.MAX_VALUE / 2)));
}
@ParameterizedTest
@MethodSource("implementations")
public void testEmptyKey(AbstractTrie<Integer> trie) throws Exception
{
assertTrue(trie.put("", -1));
assertThat(trie.get(""), is(-1));
assertThat(trie.getBest(""), is(-1));
assertThat(trie.getBest("anything"), is(-1));
assertThat(trie.getBest(BufferUtil.toBuffer("")), is(-1));
assertThat(trie.getBest(BufferUtil.toBuffer("anything")), is(-1));
assertThat(trie.getBest(BufferUtil.toBuffer("").array()), is(-1));
assertThat(trie.getBest(BufferUtil.toBuffer("anything").array()), is(-1));
for (int i = 0; i < KEYS.length; i++)
{
assertThat(trie.get(KEYS[i]), is(i));
assertThat(trie.getBest(KEYS[i] + "XYZ"), is(i));
assertThat(trie.getBest(BufferUtil.toBuffer(KEYS[i] + "XYZ")), is(i));
assertThat(trie.getBest(BufferUtil.toBuffer(KEYS[i] + "XYZ").array()), is(i));
}
}
@ParameterizedTest
@MethodSource("implementations")
public void testNullKey(AbstractTrie<Integer> trie) throws Exception
{
assertThrows(NullPointerException.class, () -> trie.put(null, -1));
}
@ParameterizedTest
@MethodSource("implementations")
public void testNullValue(AbstractTrie<Integer> trie) throws Exception
{
trie.put("null", 0);
assertTrue(trie.put("null", null));
assertThat(trie.get("null"), nullValue());
assertThat(trie.getBest("null;xxxx"), nullValue());
}
@ParameterizedTest
@MethodSource("implementations")
public void testNullChar(AbstractTrie<Integer> trie) throws Exception
{
String key = "A" + ((char)0) + "c";
trie.put(key, 103);
assertThat(trie.get(key), is(103));
assertThat(trie.getBest(key + ";xxxx"), is(103));
}
@Test
public void testArrayTrieCapacity()
{
ArrayTrie<String> trie = new ArrayTrie<>(Character.MAX_VALUE);
String huge = "x".repeat(Character.MAX_VALUE - 1);
assertTrue(trie.put(huge, "wow"));
assertThat(trie.get(huge), is("wow"));
assertThrows(IllegalArgumentException.class, () -> new ArrayTrie<String>(Character.MAX_VALUE + 1));
} }
} }

View File

@ -0,0 +1,285 @@
//
// ========================================================================
// 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 v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.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.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.jetty.http.HttpParser;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.profile.GCProfiler;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;
@State(Scope.Benchmark)
public class TrieBenchmark
{
@Param({
"ArrayTrie",
"TernaryTrie",
"ArrayTernaryTrie",
"TreeTrie",
"HashTrie",
})
public static String TRIE_TYPE;
private AbstractTrie<String> trie;
private static final String LONG_HIT = "This-is-a-Moderately-Long-Key-that-will-hit";
private static final String LONG_MISS = "This-is-a-Moderately-Long-Key-that-will-miss";
@Setup
public void setUp() throws Exception
{
boolean caseSensitive = false;
Set<Character> alphabet = new HashSet<>();
int capacity = 4096;
switch (TRIE_TYPE)
{
case "ArrayTrie":
trie = new ArrayTrie<>(caseSensitive, capacity);
break;
case "ArrayTernaryTrie":
trie = new ArrayTernaryTrie<>(caseSensitive, capacity);
break;
case "TreeTrie":
trie = new TreeTrie();
break;
case "HashTrie":
trie = new HashTrie(caseSensitive);
break;
default:
throw new AssertionError("No trie for " + TRIE_TYPE);
}
for (String k : HttpParser.CACHE.keySet())
if (!trie.put(k, HttpParser.CACHE.get(k).toString()))
throw new IllegalStateException("Could not add " + k);
trie.put(LONG_HIT, LONG_HIT);
// System.err.println("====");
// for (String k : trie.keySet())
// System.err.printf("%s: %s%n", k, trie.get(k));
// System.err.println("----");
}
@Benchmark
public boolean testPut()
{
trie.clear();
for (String k : HttpParser.CACHE.keySet())
if (!trie.put(k, HttpParser.CACHE.get(k).toString()))
return false;
return true;
}
@Benchmark
public boolean testGet()
{
if (
// short miss
trie.get("Xx") == null &&
// long miss
trie.get("Zasdfadsfasfasfbae9mn3m0mdmmfkk092nvfs0smnsmm3k23m3m23m") == null &&
// short near miss
trie.get("Pragma: no-cache0") == null &&
// long near miss
trie.get(LONG_MISS) == null &&
// short hit
trie.get("Pragma: no-cache") != null &&
// medium hit
trie.get("Accept-Language: en-US,enq=0.5") != null &&
// long hit
trie.get(LONG_HIT) != null
)
return true;
throw new IllegalStateException();
}
private static final ByteBuffer X = BufferUtil.toBuffer("Xx\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
private static final ByteBuffer Z = BufferUtil.toBuffer("Zasdfadsfasfasfbae9mn3m0mdmmfkk092nvfs0smnsmm3k23m3m23m\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
private static final ByteBuffer M = BufferUtil.toBuffer(LONG_MISS + ";xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
private static final ByteBuffer P = BufferUtil.toBuffer("Pragma: no-cache;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
private static final ByteBuffer A = BufferUtil.toBuffer("Accept-Language: en-US,enq=0.5;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
private static final ByteBuffer H = BufferUtil.toBuffer(LONG_HIT + ";xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
@Benchmark
public boolean testGetBest()
{
if (
// short miss
trie.getBest(X) == null &&
// long miss
trie.getBest(Z) == null &&
// long near miss
trie.getBest(M) == null &&
// short hit
trie.getBest(P) != null &&
// medium hit
trie.getBest(A) != null &&
// long hit
trie.getBest(H) != null)
return true;
throw new IllegalStateException();
}
private class HashTrie extends AbstractTrie<String>
{
Map<String, String> _contents;
public HashTrie(boolean caseSensitive)
{
super(caseSensitive);
_contents = new HashMap<>();
}
@Override
public String get(String s)
{
if (isCaseInsensitive())
s = StringUtil.asciiToLowerCase(s);
return _contents.get(s);
}
@Override
public String get(String s, int offset, int len)
{
throw new UnsupportedOperationException();
}
@Override
public String get(ByteBuffer b, int offset, int len)
{
throw new UnsupportedOperationException();
}
@Override
public String getBest(String s, int offset, int len)
{
throw new UnsupportedOperationException();
}
@Override
public String getBest(ByteBuffer b, int offset, int len)
{
throw new UnsupportedOperationException();
}
@Override
public String getBest(ByteBuffer buf)
{
int len = buf.remaining();
for (int i = 0; i < len; i++)
{
byte b = buf.get(buf.position() + i);
switch (b)
{
case '\r':
case '\n':
case ';':
String s = BufferUtil.toString(buf, buf.position(), i, StandardCharsets.ISO_8859_1);
if (isCaseInsensitive())
s = StringUtil.asciiToLowerCase(s);
return trie.get(s);
default:
}
}
throw new IllegalStateException();
}
@Override
public boolean isEmpty()
{
throw new UnsupportedOperationException();
}
@Override
public int size()
{
throw new UnsupportedOperationException();
}
@Override
public Set<String> keySet()
{
return _contents.keySet();
}
@Override
public boolean put(String s, String v)
{
if (isCaseInsensitive())
s = StringUtil.asciiToLowerCase(s);
return _contents.put(s, v) == null;
}
@Override
public void clear()
{
_contents.clear();
}
}
// public static void main(String... args) throws Exception
// {
// TrieBenchmark.TRIE_TYPE = "HashTrie";
// TrieBenchmark b = new TrieBenchmark();
// b.setUp();
// b.testGet();
// b.testGetBest();
// b.testPut();
// }
public static void main(String[] args) throws RunnerException
{
Options opt = new OptionsBuilder()
.include(TrieBenchmark.class.getSimpleName())
.warmupIterations(3)
.warmupTime(TimeValue.seconds(5))
.measurementIterations(3)
.measurementTime(TimeValue.seconds(5))
.forks(1)
.threads(1)
.addProfiler(GCProfiler.class)
.build();
new Runner(opt).run();
}
}