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:
parent
480767a03b
commit
51120b1f0b
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue