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

View File

@ -15,9 +15,11 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import java.util.function.Function;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
/**
*
@ -71,7 +73,7 @@ public enum HttpHeaderValue
return _string;
}
private static EnumSet<HttpHeader> __known =
private static final EnumSet<HttpHeader> __known =
EnumSet.of(HttpHeader.CONNECTION,
HttpHeader.TRANSFER_ENCODING,
HttpHeader.CONTENT_ENCODING);
@ -82,4 +84,89 @@ public enum HttpHeaderValue
return false;
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, deflate"))
.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-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"))
@ -1058,19 +1057,11 @@ public class HttpParser
if (addToFieldCache && _header != null && _valueString != null)
{
if (_fieldCache == null)
{
_fieldCache = (getHeaderCacheSize() > 0 && (_version != null && _version == HttpVersion.HTTP_1_1))
? new Index.Builder<HttpField>()
.caseSensitive(false)
.mutable()
.maxCapacity(getHeaderCacheSize())
.build()
: NO_CACHE;
}
_fieldCache = Index.buildCaseSensitiveMutableVisibleAsciiAlphabet(getHeaderCacheSize());
if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
if (!_fieldCache.put(_field))
if (_field.getValue().length() < getHeaderCacheSize() && !_fieldCache.put(_field))
{
_fieldCache.clear();
_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.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -2971,4 +2972,82 @@ public class HttpParserTest
_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;
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 static final Comparator<String> COMPARATOR = new CipherComparator();
private static final Index<Boolean> __blackProtocols = new Index.Builder<Boolean>()
.caseSensitive(false)
.with("TLSv1.2", Boolean.TRUE)
.with("TLSv1.1", Boolean.TRUE)
.with("TLSv1", Boolean.TRUE)
.with("SSL", Boolean.TRUE)
.with("SSLv2", Boolean.TRUE)
.with("SSLv3", Boolean.TRUE)
.build();
private static final Set<String> __blackProtocols = Stream.of(
"TLSv1.2",
"TLSv1.1",
"TLSv1",
"SSL",
"SSLv2",
"SSLv3"
).map(StringUtil::asciiToUpperCase).collect(Collectors.toSet());
private static final Index<Boolean> __blackCiphers = new Index.Builder<Boolean>()
.caseSensitive(false)
.with("TLS_NULL_WITH_NULL_NULL", Boolean.TRUE)
.with("TLS_RSA_WITH_NULL_MD5", Boolean.TRUE)
.with("TLS_RSA_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_RSA_EXPORT_WITH_RC4_40_MD5", Boolean.TRUE)
.with("TLS_RSA_WITH_RC4_128_MD5", Boolean.TRUE)
.with("TLS_RSA_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", Boolean.TRUE)
.with("TLS_RSA_WITH_IDEA_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_WITH_DES_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_DES_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_DES_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_DES_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_DES_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", Boolean.TRUE)
.with("TLS_DH_anon_WITH_RC4_128_MD5", Boolean.TRUE)
.with("TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_anon_WITH_DES_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_KRB5_WITH_DES_CBC_SHA", Boolean.TRUE)
.with("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_KRB5_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_KRB5_WITH_IDEA_CBC_SHA", Boolean.TRUE)
.with("TLS_KRB5_WITH_DES_CBC_MD5", Boolean.TRUE)
.with("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", Boolean.TRUE)
.with("TLS_KRB5_WITH_RC4_128_MD5", Boolean.TRUE)
.with("TLS_KRB5_WITH_IDEA_CBC_MD5", Boolean.TRUE)
.with("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", Boolean.TRUE)
.with("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", Boolean.TRUE)
.with("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", Boolean.TRUE)
.with("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", Boolean.TRUE)
.with("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", Boolean.TRUE)
.with("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", Boolean.TRUE)
.with("TLS_PSK_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_anon_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_anon_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_WITH_NULL_SHA256", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_anon_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_anon_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
.with("TLS_PSK_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_WITH_SEED_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_SEED_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_SEED_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_SEED_CBC_SHA", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_SEED_CBC_SHA", Boolean.TRUE)
.with("TLS_DH_anon_WITH_SEED_CBC_SHA", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_DH_anon_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_DH_anon_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_PSK_WITH_NULL_SHA256", Boolean.TRUE)
.with("TLS_PSK_WITH_NULL_SHA384", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_NULL_SHA256", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_NULL_SHA384", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_NULL_SHA256", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_NULL_SHA384", Boolean.TRUE)
.with("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
.with("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDH_anon_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_ECDH_anon_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_RC4_128_SHA", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_NULL_SHA", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_NULL_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_NULL_SHA384", Boolean.TRUE)
.with("TLS_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_RSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_RSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_PSK_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_PSK_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
.with("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
.with("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_128_CCM", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_256_CCM", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_128_CCM_8", Boolean.TRUE)
.with("TLS_RSA_WITH_AES_256_CCM_8", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_128_CCM", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_256_CCM", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_128_CCM_8", Boolean.TRUE)
.with("TLS_PSK_WITH_AES_256_CCM_8", Boolean.TRUE)
.build();
private static final Set<String> __blackCiphers = Stream.of(
"TLS_NULL_WITH_NULL_NULL",
"TLS_RSA_WITH_NULL_MD5",
"TLS_RSA_WITH_NULL_SHA",
"TLS_RSA_EXPORT_WITH_RC4_40_MD5",
"TLS_RSA_WITH_RC4_128_MD5",
"TLS_RSA_WITH_RC4_128_SHA",
"TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
"TLS_RSA_WITH_IDEA_CBC_SHA",
"TLS_RSA_EXPORT_WITH_DES40_CBC_SHA",
"TLS_RSA_WITH_DES_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA",
"TLS_DH_DSS_WITH_DES_CBC_SHA",
"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA",
"TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA",
"TLS_DH_RSA_WITH_DES_CBC_SHA",
"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
"TLS_DHE_DSS_WITH_DES_CBC_SHA",
"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
"TLS_DHE_RSA_WITH_DES_CBC_SHA",
"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DH_anon_EXPORT_WITH_RC4_40_MD5",
"TLS_DH_anon_WITH_RC4_128_MD5",
"TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
"TLS_DH_anon_WITH_DES_CBC_SHA",
"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA",
"TLS_KRB5_WITH_DES_CBC_SHA",
"TLS_KRB5_WITH_3DES_EDE_CBC_SHA",
"TLS_KRB5_WITH_RC4_128_SHA",
"TLS_KRB5_WITH_IDEA_CBC_SHA",
"TLS_KRB5_WITH_DES_CBC_MD5",
"TLS_KRB5_WITH_3DES_EDE_CBC_MD5",
"TLS_KRB5_WITH_RC4_128_MD5",
"TLS_KRB5_WITH_IDEA_CBC_MD5",
"TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA",
"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA",
"TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
"TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5",
"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5",
"TLS_KRB5_EXPORT_WITH_RC4_40_MD5",
"TLS_PSK_WITH_NULL_SHA",
"TLS_DHE_PSK_WITH_NULL_SHA",
"TLS_RSA_PSK_WITH_NULL_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_DH_DSS_WITH_AES_128_CBC_SHA",
"TLS_DH_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DH_anon_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_DH_DSS_WITH_AES_256_CBC_SHA",
"TLS_DH_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DH_anon_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_NULL_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DH_DSS_WITH_AES_128_CBC_SHA256",
"TLS_DH_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DH_DSS_WITH_AES_256_CBC_SHA256",
"TLS_DH_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DH_anon_WITH_AES_128_CBC_SHA256",
"TLS_DH_anon_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA",
"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA",
"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA",
"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA",
"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA",
"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA",
"TLS_PSK_WITH_RC4_128_SHA",
"TLS_PSK_WITH_3DES_EDE_CBC_SHA",
"TLS_PSK_WITH_AES_128_CBC_SHA",
"TLS_PSK_WITH_AES_256_CBC_SHA",
"TLS_DHE_PSK_WITH_RC4_128_SHA",
"TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA",
"TLS_DHE_PSK_WITH_AES_128_CBC_SHA",
"TLS_DHE_PSK_WITH_AES_256_CBC_SHA",
"TLS_RSA_PSK_WITH_RC4_128_SHA",
"TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_PSK_WITH_AES_128_CBC_SHA",
"TLS_RSA_PSK_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_SEED_CBC_SHA",
"TLS_DH_DSS_WITH_SEED_CBC_SHA",
"TLS_DH_RSA_WITH_SEED_CBC_SHA",
"TLS_DHE_DSS_WITH_SEED_CBC_SHA",
"TLS_DHE_RSA_WITH_SEED_CBC_SHA",
"TLS_DH_anon_WITH_SEED_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DH_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DH_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DH_DSS_WITH_AES_128_GCM_SHA256",
"TLS_DH_DSS_WITH_AES_256_GCM_SHA384",
"TLS_DH_anon_WITH_AES_128_GCM_SHA256",
"TLS_DH_anon_WITH_AES_256_GCM_SHA384",
"TLS_PSK_WITH_AES_128_GCM_SHA256",
"TLS_PSK_WITH_AES_256_GCM_SHA384",
"TLS_RSA_PSK_WITH_AES_128_GCM_SHA256",
"TLS_RSA_PSK_WITH_AES_256_GCM_SHA384",
"TLS_PSK_WITH_AES_128_CBC_SHA256",
"TLS_PSK_WITH_AES_256_CBC_SHA384",
"TLS_PSK_WITH_NULL_SHA256",
"TLS_PSK_WITH_NULL_SHA384",
"TLS_DHE_PSK_WITH_AES_128_CBC_SHA256",
"TLS_DHE_PSK_WITH_AES_256_CBC_SHA384",
"TLS_DHE_PSK_WITH_NULL_SHA256",
"TLS_DHE_PSK_WITH_NULL_SHA384",
"TLS_RSA_PSK_WITH_AES_128_CBC_SHA256",
"TLS_RSA_PSK_WITH_AES_256_CBC_SHA384",
"TLS_RSA_PSK_WITH_NULL_SHA256",
"TLS_RSA_PSK_WITH_NULL_SHA384",
"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
"TLS_ECDH_ECDSA_WITH_NULL_SHA",
"TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_NULL_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDH_RSA_WITH_NULL_SHA",
"TLS_ECDH_RSA_WITH_RC4_128_SHA",
"TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_NULL_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDH_anon_WITH_NULL_SHA",
"TLS_ECDH_anon_WITH_RC4_128_SHA",
"TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
"TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
"TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA",
"TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA",
"TLS_SRP_SHA_WITH_AES_128_CBC_SHA",
"TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA",
"TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA",
"TLS_SRP_SHA_WITH_AES_256_CBC_SHA",
"TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA",
"TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_PSK_WITH_RC4_128_SHA",
"TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_PSK_WITH_NULL_SHA",
"TLS_ECDHE_PSK_WITH_NULL_SHA256",
"TLS_ECDHE_PSK_WITH_NULL_SHA384",
"TLS_RSA_WITH_ARIA_128_CBC_SHA256",
"TLS_RSA_WITH_ARIA_256_CBC_SHA384",
"TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256",
"TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384",
"TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256",
"TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384",
"TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256",
"TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384",
"TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384",
"TLS_DH_anon_WITH_ARIA_128_CBC_SHA256",
"TLS_DH_anon_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256",
"TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384",
"TLS_RSA_WITH_ARIA_128_GCM_SHA256",
"TLS_RSA_WITH_ARIA_256_GCM_SHA384",
"TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256",
"TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384",
"TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256",
"TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384",
"TLS_DH_anon_WITH_ARIA_128_GCM_SHA256",
"TLS_DH_anon_WITH_ARIA_256_GCM_SHA384",
"TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384",
"TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256",
"TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384",
"TLS_PSK_WITH_ARIA_128_CBC_SHA256",
"TLS_PSK_WITH_ARIA_256_CBC_SHA384",
"TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256",
"TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384",
"TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256",
"TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384",
"TLS_PSK_WITH_ARIA_128_GCM_SHA256",
"TLS_PSK_WITH_ARIA_256_GCM_SHA384",
"TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256",
"TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384",
"TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256",
"TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256",
"TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384",
"TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
"TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
"TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256",
"TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384",
"TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256",
"TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384",
"TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384",
"TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
"TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
"TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256",
"TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384",
"TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256",
"TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384",
"TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_RSA_WITH_AES_128_CCM",
"TLS_RSA_WITH_AES_256_CCM",
"TLS_RSA_WITH_AES_128_CCM_8",
"TLS_RSA_WITH_AES_256_CCM_8",
"TLS_PSK_WITH_AES_128_CCM",
"TLS_PSK_WITH_AES_256_CCM",
"TLS_PSK_WITH_AES_128_CCM_8",
"TLS_PSK_WITH_AES_256_CCM_8"
).map(StringUtil::asciiToUpperCase).collect(Collectors.toSet());
public static boolean isBlackListProtocol(String tlsProtocol)
{
return __blackProtocols.get(tlsProtocol) != null;
return __blackProtocols.contains(StringUtil.asciiToUpperCase(tlsProtocol));
}
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:
{
if (HttpVersion.HTTP_1_1.equals(_requestBuilder.version()))
if (!HttpHeaderValue.parseCsvIndex(value, t ->
{
HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
if (expect == HttpHeaderValue.CONTINUE)
switch (t)
{
case CONTINUE:
_expect100Continue = true;
}
else if (expect == HttpHeaderValue.PROCESSING)
{
return true;
case PROCESSING:
_expect102Processing = true;
return true;
default:
return false;
}
else
}, s -> 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;
}
}
_expect100Continue = false;
_expect102Processing = false;
}
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 final HttpChannel _channel;
@ -1019,6 +1019,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (isClosed())
throw new IOException("Closed");
s = String.valueOf(s);
String charset = _channel.getResponse().getCharacterEncoding();
CharsetEncoder encoder = _encoder.get();
if (encoder == null || !encoder.charset().name().equalsIgnoreCase(charset))

View File

@ -288,7 +288,7 @@ public class Request implements HttpServletRequest
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_RANGE,
HttpHeader.IF_UNMODIFIED_SINCE,
@ -853,7 +853,7 @@ public class Request implements HttpServletRequest
public long getDateHeader(String name)
{
HttpFields fields = _httpFields;
return fields == null ? null : fields.getDateField(name);
return fields == null ? -1 : fields.getDateField(name);
}
@Override
@ -1062,7 +1062,7 @@ public class Request implements HttpServletRequest
List<String> vals = getParameters().getValues(name);
if (vals == null)
return null;
return vals.toArray(new String[vals.size()]);
return vals.toArray(new String[0]);
}
public MultiMap<String> getQueryParameters()

View File

@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -31,16 +32,21 @@ import java.util.stream.Collectors;
*/
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()
{
return _caseInsensitive;
return !_caseSensitive;
}
public boolean isCaseSensitive()
{
return _caseSensitive;
}
public boolean put(V v)
@ -84,16 +90,27 @@ abstract class AbstractTrie<V> implements Index.Mutable<V>
* <li>utf16</li>
* <li>utf8</li>
* </ul>
* The tree has 10 nodes as follows:
* The tree switching by character is:
* <pre>
* 1 - 6
* /
* _ - 8
* /
* u - t - f - 1 - 6
* root - u - t - f - 1 - 6
* \
* 8
* </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 caseSensitive true if the capacity should be calculated with case-sensitive keys
* @return The capacity in nodes of a tree decomposition
@ -104,7 +121,7 @@ abstract class AbstractTrie<V> implements Index.Mutable<V>
? new ArrayList<>(keys)
: keys.stream().map(String::toLowerCase).collect(Collectors.toList());
Collections.sort(list);
return AbstractTrie.requiredCapacity(list, 0, list.size(), 0);
return 1 + AbstractTrie.requiredCapacity(list, 0, list.size(), 0);
}
/**
@ -119,6 +136,8 @@ abstract class AbstractTrie<V> implements Index.Mutable<V>
{
int required = 0;
while (true)
{
// Examine all the keys in the subtree
Character nodeChar = null;
for (int i = 0; i < length; i++)
@ -156,8 +175,23 @@ abstract class AbstractTrie<V> implements Index.Mutable<V>
// If we finish the iteration with a nodeChar, then we must add the required nodes for the subtree under it.
if (nodeChar != null)
required += AbstractTrie.requiredCapacity(keys, offset, length, index + 1);
{
// instead of recursion here, we loop to avoid tail recursion
index++;
continue;
}
return required;
}
}
protected boolean putAll(Map<String, V> contents)
{
for (Map.Entry<String, V> entry : contents.entrySet())
{
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
*/
@Deprecated
class ArrayTernaryTrie<V> extends AbstractTrie<V>
{
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
* 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
@ -99,18 +100,15 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
/**
* 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
* 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.
* 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".
* @see AbstractTrie#requiredCapacity(Set, boolean)
*/
@SuppressWarnings("unchecked")
ArrayTernaryTrie(boolean insensitive, int capacity)
ArrayTernaryTrie(boolean caseSensitive, int capacity)
{
super(insensitive);
super(caseSensitive);
if (capacity > MAX_CAPACITY)
throw new IllegalArgumentException("ArrayTernaryTrie maximum capacity overflow (" + capacity + " > " + MAX_CAPACITY + ")");
_value = (V[])new Object[capacity];
@ -118,28 +116,6 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
_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
public void clear()
{
@ -158,8 +134,8 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
for (int k = 0; k < limit; k++)
{
char c = s.charAt(k);
if (isCaseInsensitive() && c < 128)
c = StringUtil.lowercases[c];
if (isCaseInsensitive())
c = StringUtil.asciiToLowerCase(c);
while (true)
{
@ -169,7 +145,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
if (t == _rows)
{
_rows++;
if (_rows >= _key.length)
if (_rows > _key.length)
{
_rows--;
return false;
@ -202,7 +178,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
if (t == _rows)
{
_rows++;
if (_rows >= _key.length)
if (_rows > _key.length)
{
_rows--;
return false;
@ -223,8 +199,8 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
for (int i = 0; i < len; )
{
char c = s.charAt(offset + i++);
if (isCaseInsensitive() && c < 128)
c = StringUtil.lowercases[c];
if (isCaseInsensitive())
c = StringUtil.asciiToLowerCase(c);
while (true)
{
@ -259,7 +235,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
{
byte c = (byte)(b.get(offset + i++) & 0x7f);
if (isCaseInsensitive())
c = (byte)StringUtil.lowercases[c];
c = StringUtil.asciiToLowerCase(c);
while (true)
{
@ -305,8 +281,8 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
{
char c = s.charAt(offset++);
len--;
if (isCaseInsensitive() && c < 128)
c = StringUtil.lowercases[c];
if (isCaseInsensitive())
c = StringUtil.asciiToLowerCase(c);
while (true)
{
@ -363,7 +339,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
byte c = (byte)(b[offset++] & 0x7f);
len--;
if (isCaseInsensitive())
c = (byte)StringUtil.lowercases[c];
c = StringUtil.asciiToLowerCase(c);
while (true)
{
@ -406,7 +382,7 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
{
byte c = (byte)(b.get(o + i) & 0x7f);
if (isCaseInsensitive())
c = (byte)StringUtil.lowercases[c];
c = StringUtil.asciiToLowerCase(c);
while (true)
{
@ -443,20 +419,20 @@ class ArrayTernaryTrie<V> extends AbstractTrie<V>
public String toString()
{
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++)
{
if (_key[r] != null && _value[r] != null)
{
if (r != 0)
buf.append(',');
buf.append(_key[r]);
buf.append('=');
buf.append(_value[r].toString());
buf.append(String.valueOf(_value[r]));
}
}
if (buf.length() == 0)
return "{}";
buf.setCharAt(0, '{');
buf.append('}');
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 ArrayTernaryTrie<V> _trie;
Growing(boolean insensitive, int capacity, int growby)
public Growing(boolean insensitive, int capacity, int growby)
{
super(insensitive);
_growby = growby;

View File

@ -13,12 +13,12 @@
package org.eclipse.jetty.util;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* <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
* must use a big character table.
* </p>
* <p>This Trie is very space efficient if the key characters are
* from ' ', '+', '-', ':', ';', '.', 'A' to 'Z' or 'a' to 'z'.
* <p>This Trie is space efficient if the key characters are
* from ' ', '+', '-', ':', ';', '.', '0' - '9', A' to 'Z' or 'a' to 'z'
* Other ISO-8859-1 characters can be used by the key, but less space
* efficiently.
* </p>
@ -45,221 +45,331 @@ import java.util.Set;
*/
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
* up directly without going to a big index. This is set at
* 32 to cover case insensitive alphabet and a few other common
* 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
* (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, -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, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
/*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -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
/*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, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
/*5*/ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,-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,
};
/**
* 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
* to the next row in the Trie. This is actually a 2 dimensional
* array that has been flattened to achieve locality of reference.
* 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
* _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 array is of characters rather than integers to save space.
*/
private final char[] _rowIndex;
/**
* The key (if any) for a Trie row.
* 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 final char[] _table;
private final int[] _lookup;
private final Node<V>[] _node;
private final int _bigRowSize;
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
* 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.
* 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".
* is the total number of characters of all keys stored in the Trie,
* plus 1 for the empty key.
* @see AbstractTrie#requiredCapacity(Set, boolean)
*/
@SuppressWarnings("unchecked")
ArrayTrie(int capacity)
{
super(true);
capacity++;
_value = (V[])new Object[capacity];
_rowIndex = new char[capacity * ROW_SIZE];
_key = new String[capacity];
this(false, capacity);
}
@SuppressWarnings("unchecked")
ArrayTrie(Map<String, V> initialValues)
ArrayTrie(boolean caseSensitive, int capacity)
{
super(true);
// The calculated requiredCapacity does not take into account the
// extra reserved slot for the empty string key, so we have to add 1.
int capacity = requiredCapacity(initialValues.keySet(), false) + 1;
_value = (V[])new Object[capacity];
_rowIndex = 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);
}
super(caseSensitive);
_bigRowSize = caseSensitive ? BIG_ROW_SENSITIVE : BIG_ROW_INSENSITIVE;
if (capacity > MAX_CAPACITY)
throw new IllegalArgumentException("Capacity " + capacity + " > " + MAX_CAPACITY);
_lookup = !caseSensitive ? LOOKUP_INSENSITIVE : LOOKUP_SENSITIVE;
_table = new char[capacity * ROW_SIZE];
_node = new Node[capacity];
}
@Override
public void clear()
{
_rows = 0;
Arrays.fill(_value, null);
Arrays.fill(_rowIndex, (char)0);
Arrays.fill(_key, null);
Arrays.fill(_table, (char)0);
Arrays.fill(_node, null);
}
@Override
public boolean put(String s, V v)
public boolean put(String key, V value)
{
int t = 0;
int k;
int limit = s.length();
for (k = 0; k < limit; k++)
int row = 0;
int limit = key.length();
for (int i = 0; i < limit; i++)
{
char c = s.charAt(k);
int index = LOOKUP[c & 0x7f];
if (index >= 0)
char c = key.charAt(i);
int column = c > 0x7f ? Integer.MIN_VALUE : _lookup[c];
if (column >= 0)
{
int idx = t * ROW_SIZE + index;
t = _rowIndex[idx];
if (t == 0)
// This character is indexed to a column of the main table
int idx = row * ROW_SIZE + column;
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;
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
{
if (_bigIndex == null)
_bigIndex = new char[_value.length][];
if (t >= _bigIndex.length)
// This char is neither in the normal table, nor the first part of a bigRow
// Look for it linearly in an extended big row.
int last = row;
row = 0;
Node<V> node = _node[last];
if (node != null)
{
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;
char[] big = _bigIndex[t];
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 = _bigIndex[t] = new char[128];
t = big[c];
if (t == 0)
{
if (_rows == _value.length)
return false;
t = big[c] = ++_rows;
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)
{
_rows = (char)_key.length;
return false;
}
_key[t] = v == null ? null : s;
_value[t] = v;
// 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)
{
int column = _lookup[c];
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;
}
}
// Not an indexed char, so do a linear search through he tail of the bigRow
Node<V> node = _node[row];
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
public V get(String s, int offset, int len)
{
int t = 0;
int row = 0;
for (int i = 0; i < len; i++)
{
char c = s.charAt(offset + i);
int index = LOOKUP[c & 0x7f];
if (index >= 0)
{
int idx = t * ROW_SIZE + index;
t = _rowIndex[idx];
if (t == 0)
row = lookup(row, c);
if (row < 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
public V get(ByteBuffer b, int offset, int len)
{
int t = 0;
int row = 0;
for (int i = 0; i < len; i++)
{
byte c = b.get(offset + i);
int index = LOOKUP[c & 0x7f];
if (index >= 0)
{
int idx = t * ROW_SIZE + index;
t = _rowIndex[idx];
if (t == 0)
row = lookup(row, (char)(c & 0xff));
if (row < 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
@ -282,177 +392,108 @@ class ArrayTrie<V> extends AbstractTrie<V>
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;
for (int i = 0; i < len; i++)
{
char c = s.charAt(pos++);
int index = LOOKUP[c & 0x7f];
if (index >= 0)
{
int idx = t * ROW_SIZE + index;
int nt = _rowIndex[idx];
if (nt == 0)
int next = lookup(row, c);
if (next < 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
if (_key[t] != null)
// Is the row a match?
Node<V> node = _node[row];
if (node != null && node._key != null)
{
// 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)
return best;
return (V)_value[t];
}
}
return (V)_value[t];
return node._value;
}
private V getBest(int t, byte[] b, int offset, int len)
row = next;
}
Node<V> node = _node[row];
return node == null ? null : node._value;
}
private V getBest(int row, byte[] b, int offset, int len)
{
for (int i = 0; i < len; i++)
{
byte c = b[offset + i];
int index = LOOKUP[c & 0x7f];
if (index >= 0)
{
int idx = t * ROW_SIZE + index;
int nt = _rowIndex[idx];
if (nt == 0)
int next = lookup(row, (char)(c & 0xff));
if (next < 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
if (_key[t] != null)
// Is the next row a match?
Node<V> node = _node[row];
if (node != null && node._key != null)
{
// 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)
return best;
break;
}
}
return (V)_value[t];
return node._value;
}
private V getBest(int t, ByteBuffer b, int offset, int len)
row = next;
}
Node<V> node = _node[row];
return node == null ? null : node._value;
}
private V getBest(int row, ByteBuffer b, int offset, int len)
{
int pos = b.position() + offset;
for (int i = 0; i < len; i++)
{
byte c = b.get(pos++);
int index = LOOKUP[c & 0x7f];
if (index >= 0)
{
int idx = t * ROW_SIZE + index;
int nt = _rowIndex[idx];
if (nt == 0)
int next = lookup(row, (char)(c & 0xff));
if (next < 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
if (_key[t] != null)
// Is the next row a match?
Node<V> node = _node[row];
if (node != null && node._key != null)
{
// 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)
return best;
break;
return node._value;
}
row = next;
}
return (V)_value[t];
Node<V> node = _node[row];
return node == null ? null : node._value;
}
@Override
public String toString()
{
StringBuilder buf = new StringBuilder();
toString(buf, 0);
if (buf.length() == 0)
return "{}";
buf.setCharAt(0, '{');
buf.append('}');
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);
}
}
return
"AT@" + Integer.toHexString(hashCode()) + '{' +
"cs=" + isCaseSensitive() + ';' +
"c=" + _table.length / ROW_SIZE + ';' +
Arrays.stream(_node)
.filter(n -> n != null && n._key != null)
.map(Node::toString)
.collect(Collectors.joining(",")) +
'}';
}
@Override
public Set<String> keySet()
{
Set<String> keys = new HashSet<>();
keySet(keys, 0);
return keys;
return Arrays.stream(_node)
.filter(Objects::nonNull)
.map(n -> n._key)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
@Override
@ -467,26 +508,72 @@ class ArrayTrie<V> extends AbstractTrie<V>
return keySet().isEmpty();
}
private void keySet(Set<String> set, int t)
public void dumpStdErr()
{
if (t < _value.length && _value[t] != null)
set.add(_key[t]);
System.err.print("row:");
for (int c = 0; c < ROW_SIZE; c++)
{
for (int i = 0; i < 0x7f; i++)
{
if (_lookup[i] == c)
{
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 i = 0; i < ROW_SIZE; i++)
for (int row = 0; row <= _rows; row++)
{
int idx = t * ROW_SIZE + i;
if (idx < _rowIndex.length && _rowIndex[idx] != 0)
keySet(set, _rowIndex[idx]);
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);
}
char[] big = _bigIndex == null || t >= _bigIndex.length ? null : _bigIndex[t];
if (big != null)
{
for (int i : big)
{
if (i != 0)
keySet(set, i);
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.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* An empty trie implementation that never contains anything and never accepts new entries.
*
* @param <V> the entry type
*/
class EmptyTrie<V> extends AbstractTrie<V>
{
@SuppressWarnings("rawtypes")
private static final EmptyTrie SENSITIVE = new EmptyTrie<>(false);
private static final EmptyTrie SENSITIVE = new EmptyTrie<>(true);
@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")
public static <V> EmptyTrie<V> instance(boolean caseSensitive)
@ -34,15 +42,21 @@ class EmptyTrie<V> extends AbstractTrie<V>
return caseSensitive ? SENSITIVE : INSENSITIVE;
}
private EmptyTrie(boolean insensitive)
@Override
public void clear()
{
super(insensitive);
}
@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
@ -57,6 +71,30 @@ class EmptyTrie<V> extends AbstractTrie<V>
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
public V getBest(String s, int offset, int len)
{
@ -81,6 +119,20 @@ class EmptyTrie<V> extends AbstractTrie<V>
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
public int size()
{
@ -88,7 +140,8 @@ class EmptyTrie<V> extends AbstractTrie<V>
}
@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);
/**
* 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
* @return The value or null if not found
@ -53,7 +53,7 @@ public interface Index<V>
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 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);
/**
* 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.
* 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);
/**
* 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
*/
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.
@ -102,6 +114,18 @@ public interface Index<V>
*/
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.
*
@ -133,8 +157,8 @@ public interface Index<V>
/**
* Put an entry into the index.
*
* @param s The key for the entry
* @param v The value of the entry
* @param s The key for the entry. Must be non null, but can be empty.
* @param v The value of the entry. Must be non null.
* @return True if the index had capacity to add the field.
*/
boolean put(String s, V v);
@ -188,44 +212,59 @@ public interface Index<V>
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.
* @return a {@link Mutable} instance.
*/
public Mutable<V> build()
{
if (contents != null && maxCapacity == 0)
throw new IllegalStateException("Cannot create a mutable index with maxCapacity=0 and some contents");
if (maxCapacity == 0)
return EmptyTrie.instance(caseSensitive);
// TODO we need to consider large size and alphabet when picking a trie impl
Mutable<V> result;
if (maxCapacity > 0)
{
result = new ArrayTernaryTrie<>(!caseSensitive, maxCapacity);
// Work out needed capacity
int capacity = (contents == null) ? 0 : AbstractTrie.requiredCapacity(contents.keySet(), caseSensitive);
// check capacities
if (maxCapacity >= 0 && capacity > maxCapacity)
throw new IllegalStateException("Insufficient maxCapacity for contents");
// try all the tries
AbstractTrie<V> trie = ArrayTrie.from(maxCapacity, caseSensitive, contents);
if (trie != null)
return trie;
trie = TreeTrie.from(caseSensitive, contents);
if (trie != null)
return trie;
// Nothing suitable
throw new IllegalStateException("No suitable Trie implementation: " + this);
}
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)
/**
* 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)
{
for (Map.Entry<String, V> entry : contents.entrySet())
{
if (!result.put(entry.getKey(), entry.getValue()))
throw new AssertionError("Index capacity exceeded at " + entry.getKey());
}
}
return result;
}
}
if (maxCapacity < 0 || maxCapacity > ArrayTrie.MAX_CAPACITY)
return new TreeTrie<>(true);
if (maxCapacity == 0)
return EmptyTrie.instance(true);
return new ArrayTrie<>(true, maxCapacity);
}
/**
@ -242,7 +281,8 @@ public interface Index<V>
*/
public Builder()
{
this(false, null);
this.caseSensitive = false;
this.contents = null;
}
Builder(boolean caseSensitive, Map<String, V> contents)
@ -349,11 +389,22 @@ public interface Index<V>
if (contents == null)
return EmptyTrie.instance(caseSensitive);
// TODO we need to consider large size and alphabet when picking a trie impl
if (caseSensitive)
return new ArrayTernaryTrie<>(false, contents);
else
return new ArrayTrie<>(contents);
int capacity = AbstractTrie.requiredCapacity(contents.keySet(), caseSensitive);
AbstractTrie<V> trie = ArrayTrie.from(capacity, caseSensitive, contents);
if (trie != null)
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
public static final char[] lowercases =
private static final char[] LOWERCASES =
{
'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
@ -96,8 +95,73 @@ public class StringUtil
'\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
/**
* 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)
*
@ -117,7 +181,7 @@ public class StringUtil
char c1 = s.charAt(i);
if (c1 <= 127)
{
char c2 = lowercases[c1];
char c2 = LOWERCASES[c1];
if (c1 != c2)
{
c = s.toCharArray();
@ -129,12 +193,48 @@ public class StringUtil
while (i-- > 0)
{
if (c[i] <= 127)
c[i] = lowercases[c[i]];
c[i] = LOWERCASES[c[i]];
}
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
* special meaning in various filesystems.
@ -199,9 +299,9 @@ public class StringUtil
if (c1 != c2)
{
if (c1 <= 127)
c1 = lowercases[c1];
c1 = LOWERCASES[c1];
if (c2 <= 127)
c2 = lowercases[c2];
c2 = LOWERCASES[c2];
if (c1 != c2)
return false;
}
@ -229,9 +329,9 @@ public class StringUtil
if (c1 != c2)
{
if (c1 <= 127)
c1 = lowercases[c1];
c1 = LOWERCASES[c1];
if (c2 <= 127)
c2 = lowercases[c2];
c2 = LOWERCASES[c2];
if (c1 != c2)
return false;
}

View File

@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@ -39,7 +40,7 @@ import java.util.Set;
*/
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, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -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,
/*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 final TreeTrie<V>[] _nextIndex;
private final List<TreeTrie<V>> _nextOther = new ArrayList<>();
/** 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")
TreeTrie()
{
super(true);
_nextIndex = new TreeTrie[INDEX];
_c = 0;
this(false);
}
@SuppressWarnings("unchecked")
private TreeTrie(char c)
TreeTrie(boolean caseSensitive)
{
super(true);
_nextIndex = new TreeTrie[INDEX];
this._c = c;
super(caseSensitive);
_lookup = caseSensitive ? LOOKUP_SENSITIVE : LOOKUP_INSENSITIVE;
_root = new Node<V>((char)0);
}
@Override
public void clear()
{
Arrays.fill(_nextIndex, null);
_nextOther.clear();
_key = null;
_value = null;
Arrays.fill(_root._nextIndex, null);
_root._nextOther.clear();
_root._key = null;
_root._value = null;
}
@Override
public boolean put(String s, V v)
{
TreeTrie<V> t = this;
Node<V> t = _root;
int limit = s.length();
for (int k = 0; k < limit; 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 (t._nextIndex[index] == null)
t._nextIndex[index] = new TreeTrie<V>(c);
t._nextIndex[index] = new Node<V>(c);
t = t._nextIndex[index];
}
else
{
TreeTrie<V> n = null;
Node<V> n = null;
for (int i = t._nextOther.size(); i-- > 0; )
{
n = t._nextOther.get(i);
@ -111,7 +150,7 @@ class TreeTrie<V> extends AbstractTrie<V>
}
if (n == null)
{
n = new TreeTrie<V>(c);
n = new Node<V>(c);
t._nextOther.add(n);
}
t = n;
@ -125,11 +164,11 @@ class TreeTrie<V> extends AbstractTrie<V>
@Override
public V get(String s, int offset, int len)
{
TreeTrie<V> t = this;
Node<V> t = _root;
for (int i = 0; i < len; 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 (t._nextIndex[index] == null)
@ -138,7 +177,7 @@ class TreeTrie<V> extends AbstractTrie<V>
}
else
{
TreeTrie<V> n = null;
Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@ -157,11 +196,11 @@ class TreeTrie<V> extends AbstractTrie<V>
@Override
public V get(ByteBuffer b, int offset, int len)
{
TreeTrie<V> t = this;
Node<V> t = _root;
for (int i = 0; i < len; 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 (t._nextIndex[index] == null)
@ -170,7 +209,7 @@ class TreeTrie<V> extends AbstractTrie<V>
}
else
{
TreeTrie<V> n = null;
Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@ -189,11 +228,15 @@ class TreeTrie<V> extends AbstractTrie<V>
@Override
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++)
{
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 (t._nextIndex[index] == null)
@ -202,7 +245,7 @@ class TreeTrie<V> extends AbstractTrie<V>
}
else
{
TreeTrie<V> n = null;
Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@ -219,7 +262,7 @@ class TreeTrie<V> extends AbstractTrie<V>
if (t._key != null)
{
// 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)
return best;
break;
@ -243,11 +286,15 @@ class TreeTrie<V> extends AbstractTrie<V>
@Override
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++)
{
byte c = (byte)(0xff & s.charAt(offset + i));
int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1;
char c = s.charAt(offset + i);
int index = c < 0x7f ? _lookup[c] : -1;
if (index >= 0)
{
if (t._nextIndex[index] == null)
@ -256,7 +303,7 @@ class TreeTrie<V> extends AbstractTrie<V>
}
else
{
TreeTrie<V> n = null;
Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@ -273,7 +320,7 @@ class TreeTrie<V> extends AbstractTrie<V>
if (t._key != null)
{
// 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)
return best;
break;
@ -287,17 +334,16 @@ class TreeTrie<V> extends AbstractTrie<V>
{
if (b.hasArray())
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;
for (int i = 0; i < len; i++)
{
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 (t._nextIndex[index] == null)
@ -306,7 +352,7 @@ class TreeTrie<V> extends AbstractTrie<V>
}
else
{
TreeTrie<V> n = null;
Node<V> n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@ -323,7 +369,7 @@ class TreeTrie<V> extends AbstractTrie<V>
if (t._key != null)
{
// 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)
return best;
break;
@ -336,17 +382,16 @@ class TreeTrie<V> extends AbstractTrie<V>
public String toString()
{
StringBuilder buf = new StringBuilder();
toString(buf, this);
if (buf.length() == 0)
return "{}";
buf.setCharAt(0, '{');
buf.append("TT@").append(Integer.toHexString(hashCode())).append('{');
buf.append("ci=").append(isCaseInsensitive()).append(';');
toString(buf, _root, "");
buf.append('}');
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)
{
loop: while (true)
{
if (t != null)
{
@ -354,7 +399,8 @@ class TreeTrie<V> extends AbstractTrie<V>
{
try
{
out.append(',');
out.append(separator);
separator = ",";
out.append(t._key);
out.append('=');
out.append(t._value.toString());
@ -365,15 +411,34 @@ class TreeTrie<V> extends AbstractTrie<V>
}
}
for (int i = 0; i < INDEX; i++)
for (int i = 0; i < INDEX;)
{
if (t._nextIndex[i] != null)
toString(out, t._nextIndex[i]);
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; )
{
toString(out, t._nextOther.get(i));
// can we avoid tail recurse?
if (i == 0)
{
t = t._nextOther.get(i);
continue loop;
}
toString(out, t._nextOther.get(i), separator);
}
}
break;
}
}
@ -381,11 +446,11 @@ class TreeTrie<V> extends AbstractTrie<V>
public Set<String> keySet()
{
Set<String> keys = new HashSet<>();
keySet(keys, this);
keySet(keys, _root);
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)
{

View File

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

View File

@ -13,12 +13,13 @@
package org.eclipse.jetty.util;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.in;
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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
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()
{
List<AbstractTrie<Integer>> impls = new ArrayList<>();
impls.add(new ArrayTrie<Integer>(128));
impls.add(new ArrayTernaryTrie<Integer>(true, 128));
impls.add(new ArrayTernaryTrie.Growing<Integer>(true, 128, 128));
for (boolean caseSensitive : new boolean[] {true, false})
{
impls.add(new ArrayTrie<Integer>(caseSensitive,128));
impls.add(new ArrayTernaryTrie<Integer>(caseSensitive, 128));
impls.add(new TreeTrie<>(caseSensitive));
}
for (AbstractTrie<Integer> trie : impls)
{
trie.put("hello", 1);
trie.put("He", 2);
trie.put("HELL", 3);
trie.put("wibble", 4);
trie.put("Wobble", 5);
trie.put("foo-bar", 6);
trie.put("foo+bar", 7);
trie.put("HELL4", 8);
trie.put("", 9);
for (int i = 0; i < KEYS.length; i++)
{
if (!trie.put(KEYS[i], i))
throw new IllegalStateException();
}
}
return impls.stream().map(Arguments::of);
@ -61,159 +114,174 @@ public class TrieTest
@MethodSource("implementations")
public void testKeySet(AbstractTrie<Integer> trie) throws Exception
{
String[] values = new String[]{
"hello",
"He",
"HELL",
"wibble",
"Wobble",
"foo-bar",
"foo+bar",
"HELL4",
""
};
for (String value : values)
{
assertThat(value, is(in(trie.keySet())));
}
for (String value : KEYS)
assertThat(value, trie.keySet().contains(value), is(true));
for (String value : NOT_KEYS)
assertThat(value, trie.keySet().contains(value), is(false));
}
@ParameterizedTest
@MethodSource("implementations")
public void testGetString(AbstractTrie<Integer> trie) throws Exception
{
assertEquals(1, trie.get("hello").intValue());
assertEquals(2, trie.get("He").intValue());
assertEquals(3, trie.get("HELL").intValue());
assertEquals(4, trie.get("wibble").intValue());
assertEquals(5, trie.get("Wobble").intValue());
assertEquals(6, trie.get("foo-bar").intValue());
assertEquals(7, trie.get("foo+bar").intValue());
assertEquals(1, trie.get("Hello").intValue());
assertEquals(2, trie.get("HE").intValue());
assertEquals(3, trie.get("heLL").intValue());
assertEquals(4, trie.get("Wibble").intValue());
assertEquals(5, trie.get("wobble").intValue());
assertEquals(6, trie.get("Foo-bar").intValue());
assertEquals(7, trie.get("FOO+bar").intValue());
assertEquals(8, trie.get("HELL4").intValue());
assertEquals(9, trie.get("").intValue());
assertEquals(null, trie.get("helloworld"));
assertEquals(null, trie.get("Help"));
assertEquals(null, trie.get("Blah"));
for (int i = 0; i < KEYS.length; i++)
assertThat(Integer.toString(i), trie.get(KEYS[i]), is(i));
for (int i = 0; i < NOT_KEYS.length; i++)
assertThat(Integer.toString(i), trie.get(NOT_KEYS[i]), nullValue());
for (int i = 0; i < KEYS.length; i++)
{
String k = KEYS[i].toLowerCase();
Integer actual = trie.get(k);
if (k.equals(KEYS[i]) || trie.isCaseInsensitive())
assertThat(k, actual, is(i));
else
assertThat(k, actual, nullValue());
}
for (int i = 0; i < KEYS.length; i++)
{
String k = KEYS[i].toUpperCase();
Integer actual = trie.get(k);
if (k.equals(KEYS[i]) || trie.isCaseInsensitive())
assertThat(k, actual, is(i));
else
assertThat(k, actual, nullValue());
}
}
@ParameterizedTest
@MethodSource("implementations")
public void testGetBuffer(AbstractTrie<Integer> trie) throws Exception
{
assertEquals(1, trie.get(BufferUtil.toBuffer("xhellox"), 1, 5).intValue());
assertEquals(2, trie.get(BufferUtil.toBuffer("xhellox"), 1, 2).intValue());
assertEquals(3, trie.get(BufferUtil.toBuffer("xhellox"), 1, 4).intValue());
assertEquals(4, trie.get(BufferUtil.toBuffer("wibble"), 0, 6).intValue());
assertEquals(5, trie.get(BufferUtil.toBuffer("xWobble"), 1, 6).intValue());
assertEquals(6, trie.get(BufferUtil.toBuffer("xfoo-barx"), 1, 7).intValue());
assertEquals(7, trie.get(BufferUtil.toBuffer("xfoo+barx"), 1, 7).intValue());
assertEquals(1, trie.get(BufferUtil.toBuffer("xhellox"), 1, 5).intValue());
assertEquals(2, trie.get(BufferUtil.toBuffer("xHELLox"), 1, 2).intValue());
assertEquals(3, trie.get(BufferUtil.toBuffer("xhellox"), 1, 4).intValue());
assertEquals(4, trie.get(BufferUtil.toBuffer("Wibble"), 0, 6).intValue());
assertEquals(5, trie.get(BufferUtil.toBuffer("xwobble"), 1, 6).intValue());
assertEquals(6, trie.get(BufferUtil.toBuffer("xFOO-barx"), 1, 7).intValue());
assertEquals(7, trie.get(BufferUtil.toBuffer("xFOO+barx"), 1, 7).intValue());
assertEquals(null, trie.get(BufferUtil.toBuffer("xHelloworldx"), 1, 10));
assertEquals(null, trie.get(BufferUtil.toBuffer("xHelpx"), 1, 4));
assertEquals(null, trie.get(BufferUtil.toBuffer("xBlahx"), 1, 4));
for (int i = 0; i < KEYS.length; i++)
assertThat(Integer.toString(i), trie.get(BufferUtil.toBuffer(X_KEYS[i]), 1, KEYS[i].length()), is(i));
for (int i = 0; i < NOT_KEYS.length; i++)
assertThat(Integer.toString(i), trie.get(BufferUtil.toBuffer(X_NOT_KEYS[i]), 1, NOT_KEYS[i].length()), nullValue());
for (int i = 0; i < KEYS.length; i++)
{
String k = X_KEYS[i].toLowerCase();
Integer actual = trie.get(BufferUtil.toBuffer(k), 1, KEYS[i].length());
if (k.equals(X_KEYS[i]) || trie.isCaseInsensitive())
assertThat(k, actual, is(i));
else
assertThat(k, actual, nullValue());
}
for (int i = 0; i < KEYS.length; i++)
{
String k = X_KEYS[i].toUpperCase();
Integer actual = trie.get(BufferUtil.toBuffer(k), 1, KEYS[i].length());
if (k.equals(X_KEYS[i]) || trie.isCaseInsensitive())
assertThat(k, actual, is(i));
else
assertThat(k, actual, nullValue());
}
}
@ParameterizedTest
@MethodSource("implementations")
public void testGetDirectBuffer(AbstractTrie<Integer> trie) throws Exception
{
assertEquals(1, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 5).intValue());
assertEquals(2, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 2).intValue());
assertEquals(3, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 4).intValue());
assertEquals(4, trie.get(BufferUtil.toDirectBuffer("wibble"), 0, 6).intValue());
assertEquals(5, trie.get(BufferUtil.toDirectBuffer("xWobble"), 1, 6).intValue());
assertEquals(6, trie.get(BufferUtil.toDirectBuffer("xfoo-barx"), 1, 7).intValue());
assertEquals(7, trie.get(BufferUtil.toDirectBuffer("xfoo+barx"), 1, 7).intValue());
assertEquals(1, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 5).intValue());
assertEquals(2, trie.get(BufferUtil.toDirectBuffer("xHELLox"), 1, 2).intValue());
assertEquals(3, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 4).intValue());
assertEquals(4, trie.get(BufferUtil.toDirectBuffer("Wibble"), 0, 6).intValue());
assertEquals(5, trie.get(BufferUtil.toDirectBuffer("xwobble"), 1, 6).intValue());
assertEquals(6, trie.get(BufferUtil.toDirectBuffer("xFOO-barx"), 1, 7).intValue());
assertEquals(7, trie.get(BufferUtil.toDirectBuffer("xFOO+barx"), 1, 7).intValue());
assertEquals(null, trie.get(BufferUtil.toDirectBuffer("xHelloworldx"), 1, 10));
assertEquals(null, trie.get(BufferUtil.toDirectBuffer("xHelpx"), 1, 4));
assertEquals(null, trie.get(BufferUtil.toDirectBuffer("xBlahx"), 1, 4));
for (int i = 0; i < KEYS.length; i++)
assertThat(Integer.toString(i), trie.get(BufferUtil.toDirectBuffer(X_KEYS[i]), 1, KEYS[i].length()), is(i));
for (int i = 0; i < NOT_KEYS.length; i++)
assertThat(Integer.toString(i), trie.get(BufferUtil.toDirectBuffer(X_NOT_KEYS[i]), 1, NOT_KEYS[i].length()), nullValue());
for (int i = 0; i < KEYS.length; i++)
{
String k = X_KEYS[i].toLowerCase();
Integer actual = trie.get(BufferUtil.toDirectBuffer(k), 1, KEYS[i].length());
if (k.equals(X_KEYS[i]) || trie.isCaseInsensitive())
assertThat(k, actual, is(i));
else
assertThat(k, actual, nullValue());
}
for (int i = 0; i < KEYS.length; i++)
{
String k = X_KEYS[i].toUpperCase();
Integer actual = trie.get(BufferUtil.toDirectBuffer(k), 1, KEYS[i].length());
if (k.equals(X_KEYS[i]) || trie.isCaseInsensitive())
assertThat(k, actual, is(i));
else
assertThat(k, actual, nullValue());
}
}
@ParameterizedTest
@MethodSource("implementations")
public void testGetBestArray(AbstractTrie<Integer> trie) throws Exception
{
assertEquals(1, trie.getBest(StringUtil.getUtf8Bytes("xhelloxxxx"), 1, 8).intValue());
assertEquals(2, trie.getBest(StringUtil.getUtf8Bytes("xhelxoxxxx"), 1, 8).intValue());
assertEquals(3, trie.getBest(StringUtil.getUtf8Bytes("xhellxxxxx"), 1, 8).intValue());
assertEquals(6, trie.getBest(StringUtil.getUtf8Bytes("xfoo-barxx"), 1, 8).intValue());
assertEquals(8, trie.getBest(StringUtil.getUtf8Bytes("xhell4xxxx"), 1, 8).intValue());
for (int i = 0; i < NOT_KEYS.length; i++)
{
String k = X_NOT_KEYS[i];
Integer actual = trie.getBest(StringUtil.getUtf8Bytes(k), 1, X_NOT_KEYS[i].length() - 1);
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());
assertEquals(2, trie.getBest(StringUtil.getUtf8Bytes("xHELxoxxxx"), 1, 8).intValue());
assertEquals(3, trie.getBest(StringUtil.getUtf8Bytes("xHELLxxxxx"), 1, 8).intValue());
assertEquals(6, trie.getBest(StringUtil.getUtf8Bytes("xfoo-BARxx"), 1, 8).intValue());
assertEquals(8, trie.getBest(StringUtil.getUtf8Bytes("xHELL4xxxx"), 1, 8).intValue());
assertEquals(9, trie.getBest(StringUtil.getUtf8Bytes("xZZZZZxxxx"), 1, 8).intValue());
for (int i = 0; i < NOT_KEYS.length; i++)
{
String k = X_NOT_KEYS[i].toLowerCase();
Integer actual = trie.getBest(StringUtil.getUtf8Bytes(k), 1, X_NOT_KEYS[i].length() - 1);
String[] expectations = trie.isCaseSensitive() ? BEST_NOT_KEYS_LOWER : BEST_NOT_KEYS;
Integer expected = expectations[i] == null ? null : trie.get(expectations[i]);
assertThat(k, actual, is(expected));
}
}
@ParameterizedTest
@MethodSource("implementations")
public void testGetBestBuffer(AbstractTrie<Integer> trie) throws Exception
{
assertEquals(1, trie.getBest(BufferUtil.toBuffer("xhelloxxxx"), 1, 8).intValue());
assertEquals(2, trie.getBest(BufferUtil.toBuffer("xhelxoxxxx"), 1, 8).intValue());
assertEquals(3, trie.getBest(BufferUtil.toBuffer("xhellxxxxx"), 1, 8).intValue());
assertEquals(6, trie.getBest(BufferUtil.toBuffer("xfoo-barxx"), 1, 8).intValue());
assertEquals(8, trie.getBest(BufferUtil.toBuffer("xhell4xxxx"), 1, 8).intValue());
for (int i = 0; i < NOT_KEYS.length; i++)
{
String k = X_NOT_KEYS[i];
Integer actual = trie.getBest(BufferUtil.toBuffer(k), 1, X_NOT_KEYS[i].length() - 1);
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());
assertEquals(2, trie.getBest(BufferUtil.toBuffer("xHELxoxxxx"), 1, 8).intValue());
assertEquals(3, trie.getBest(BufferUtil.toBuffer("xHELLxxxxx"), 1, 8).intValue());
assertEquals(6, trie.getBest(BufferUtil.toBuffer("xfoo-BARxx"), 1, 8).intValue());
assertEquals(8, trie.getBest(BufferUtil.toBuffer("xHELL4xxxx"), 1, 8).intValue());
assertEquals(9, trie.getBest(BufferUtil.toBuffer("xZZZZZxxxx"), 1, 8).intValue());
ByteBuffer buffer = (ByteBuffer)BufferUtil.toBuffer("xhelloxxxxxxx").position(2);
assertEquals(1, trie.getBest(buffer, -1, 10).intValue());
for (int i = 0; i < NOT_KEYS.length; i++)
{
String k = X_NOT_KEYS[i].toLowerCase();
Integer actual = trie.getBest(BufferUtil.toBuffer(k), 1, X_NOT_KEYS[i].length() - 1);
String[] expectations = trie.isCaseSensitive() ? BEST_NOT_KEYS_LOWER : BEST_NOT_KEYS;
Integer expected = expectations[i] == null ? null : trie.get(expectations[i]);
assertThat(k, actual, is(expected));
}
}
@ParameterizedTest
@MethodSource("implementations")
public void testGetBestDirectBuffer(AbstractTrie<Integer> trie) throws Exception
{
assertEquals(1, trie.getBest(BufferUtil.toDirectBuffer("xhelloxxxx"), 1, 8).intValue());
assertEquals(2, trie.getBest(BufferUtil.toDirectBuffer("xhelxoxxxx"), 1, 8).intValue());
assertEquals(3, trie.getBest(BufferUtil.toDirectBuffer("xhellxxxxx"), 1, 8).intValue());
assertEquals(6, trie.getBest(BufferUtil.toDirectBuffer("xfoo-barxx"), 1, 8).intValue());
assertEquals(8, trie.getBest(BufferUtil.toDirectBuffer("xhell4xxxx"), 1, 8).intValue());
for (int i = 0; i < NOT_KEYS.length; i++)
{
String k = X_NOT_KEYS[i];
Integer actual = trie.getBest(BufferUtil.toDirectBuffer(k), 1, X_NOT_KEYS[i].length() - 1);
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());
assertEquals(2, trie.getBest(BufferUtil.toDirectBuffer("xHELxoxxxx"), 1, 8).intValue());
assertEquals(3, trie.getBest(BufferUtil.toDirectBuffer("xHELLxxxxx"), 1, 8).intValue());
assertEquals(6, trie.getBest(BufferUtil.toDirectBuffer("xfoo-BARxx"), 1, 8).intValue());
assertEquals(8, trie.getBest(BufferUtil.toDirectBuffer("xHELL4xxxx"), 1, 8).intValue());
assertEquals(9, trie.getBest(BufferUtil.toDirectBuffer("xZZZZZxxxx"), 1, 8).intValue());
for (int i = 0; i < NOT_KEYS.length; i++)
{
String k = X_NOT_KEYS[i].toLowerCase();
Integer actual = trie.getBest(BufferUtil.toDirectBuffer(k), 1, X_NOT_KEYS[i].length() - 1);
String[] expectations = trie.isCaseSensitive() ? BEST_NOT_KEYS_LOWER : BEST_NOT_KEYS;
Integer expected = expectations[i] == null ? null : trie.get(expectations[i]);
assertThat(k, actual, is(expected));
}
}
ByteBuffer buffer = (ByteBuffer)BufferUtil.toDirectBuffer("xhelloxxxxxxx").position(2);
assertEquals(1, trie.getBest(buffer, -1, 10).intValue());
@ParameterizedTest
@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
@ -232,29 +300,98 @@ public class TrieTest
@Test
public void testRequiredCapacity()
{
assertThat(requiredCapacity(Set.of("ABC", "abc"), true), is(6));
assertThat(requiredCapacity(Set.of("ABC", "abc"), false), is(3));
assertThat(requiredCapacity(Set.of(""), false), is(0));
assertThat(requiredCapacity(Set.of("ABC", ""), false), is(3));
assertThat(requiredCapacity(Set.of("ABC"), false), is(3));
assertThat(requiredCapacity(Set.of("ABC", "XYZ"), false), is(6));
assertThat(requiredCapacity(Set.of("A00", "A11"), false), is(5));
assertThat(requiredCapacity(Set.of("A00", "A01", "A10", "A11"), false), is(7));
assertThat(requiredCapacity(Set.of("A", "AB"), false), is(2));
assertThat(requiredCapacity(Set.of("A", "ABC"), false), is(3));
assertThat(requiredCapacity(Set.of("A", "ABCD"), false), is(4));
assertThat(requiredCapacity(Set.of("AB", "ABC"), false), is(3));
assertThat(requiredCapacity(Set.of("ABC", "ABCD"), false), is(4));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(6));
assertThat(requiredCapacity(Set.of("AB", "A"), false), is(2));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(6));
assertThat(requiredCapacity(Set.of("ABCDEF", "ABC"), false), is(6));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF", "ABX"), false), is(7));
assertThat(requiredCapacity(Set.of("ABCDEF", "ABC", "ABX"), false), is(7));
assertThat(requiredCapacity(Set.of("ADEF", "AQPR4", "AQZ"), false), is(9));
assertThat(requiredCapacity(Set.of("111", "ADEF", "AQPR4", "AQZ", "999"), false), is(15));
assertThat(requiredCapacity(Set.of("utf-16", "utf-8"), false), is(7));
assertThat(requiredCapacity(Set.of("utf-16", "utf-8", "utf16", "utf8"), false), is(10));
assertThat(requiredCapacity(Set.of("utf-8", "utf8", "utf-16", "utf16", "iso-8859-1", "iso_8859_1"), false), is(27));
assertThat(requiredCapacity(Set.of("ABC", "abc"), true), is(1 + 6));
assertThat(requiredCapacity(Set.of("ABC", "abc"), false), is(1 + 3));
assertThat(requiredCapacity(Set.of(""), false), is(1 + 0));
assertThat(requiredCapacity(Set.of("ABC", ""), false), is(1 + 3));
assertThat(requiredCapacity(Set.of("ABC"), false), is(1 + 3));
assertThat(requiredCapacity(Set.of("ABC", "XYZ"), false), is(1 + 6));
assertThat(requiredCapacity(Set.of("A00", "A11"), false), is(1 + 5));
assertThat(requiredCapacity(Set.of("A00", "A01", "A10", "A11"), false), is(1 + 7));
assertThat(requiredCapacity(Set.of("A", "AB"), false), is(1 + 2));
assertThat(requiredCapacity(Set.of("A", "ABC"), false), is(1 + 3));
assertThat(requiredCapacity(Set.of("A", "ABCD"), false), is(1 + 4));
assertThat(requiredCapacity(Set.of("AB", "ABC"), false), is(1 + 3));
assertThat(requiredCapacity(Set.of("ABC", "ABCD"), false), is(1 + 4));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(1 + 6));
assertThat(requiredCapacity(Set.of("AB", "A"), false), is(1 + 2));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(1 + 6));
assertThat(requiredCapacity(Set.of("ABCDEF", "ABC"), false), is(1 + 6));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF", "ABX"), false), is(1 + 7));
assertThat(requiredCapacity(Set.of("ABCDEF", "ABC", "ABX"), false), is(1 + 7));
assertThat(requiredCapacity(Set.of("ADEF", "AQPR4", "AQZ"), false), is(1 + 9));
assertThat(requiredCapacity(Set.of("111", "ADEF", "AQPR4", "AQZ", "999"), false), is(1 + 15));
assertThat(requiredCapacity(Set.of("utf-16", "utf-8"), false), is(1 + 7));
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(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();
}
}