From 51120b1f0bda1a61e60079a8ab4d73d50700aa51 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Tue, 5 Jan 2021 12:52:34 +0100
Subject: [PATCH] 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
---
.../org/eclipse/jetty/http/HttpField.java | 16 +-
.../eclipse/jetty/http/HttpHeaderValue.java | 89 ++-
.../org/eclipse/jetty/http/HttpParser.java | 13 +-
.../eclipse/jetty/http/HttpParserTest.java | 79 +++
.../org/eclipse/jetty/http2/HTTP2Cipher.java | 583 ++++++++--------
.../jetty/server/HttpChannelOverHttp.java | 37 +-
.../org/eclipse/jetty/server/HttpOutput.java | 4 +-
.../org/eclipse/jetty/server/Request.java | 6 +-
.../org/eclipse/jetty/util/AbstractTrie.java | 120 ++--
.../eclipse/jetty/util/ArrayTernaryTrie.java | 75 +-
.../org/eclipse/jetty/util/ArrayTrie.java | 643 ++++++++++--------
.../org/eclipse/jetty/util/EmptyTrie.java | 67 +-
.../java/org/eclipse/jetty/util/Index.java | 133 ++--
.../org/eclipse/jetty/util/StringUtil.java | 116 +++-
.../java/org/eclipse/jetty/util/TreeTrie.java | 213 ++++--
.../org/eclipse/jetty/util/IndexTest.java | 50 +-
.../java/org/eclipse/jetty/util/TrieTest.java | 441 +++++++-----
.../org/eclipse/jetty/util/TrieBenchmark.java | 285 ++++++++
18 files changed, 1951 insertions(+), 1019 deletions(-)
create mode 100644 tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/TrieBenchmark.java
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
index 1fa856e90f0..9032e457159 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
@@ -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;
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java
index 650ae8ec87f..f609ac8f5ed 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java
@@ -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 __known =
+ private static final EnumSet __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 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 found, Function 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;
+ }
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
index 6a25b962062..10a7f68940f 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
@@ -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()
- .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);
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
index e4fcf785650..591805775b6 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
@@ -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 list = new ArrayList<>();
+ final List 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());
+ }
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Cipher.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Cipher.java
index 3fc044fe224..b252f23bc9d 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Cipher.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Cipher.java
@@ -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 COMPARATOR = new CipherComparator();
- private static final Index __blackProtocols = new Index.Builder()
- .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 __blackProtocols = Stream.of(
+ "TLSv1.2",
+ "TLSv1.1",
+ "TLSv1",
+ "SSL",
+ "SSLv2",
+ "SSLv3"
+ ).map(StringUtil::asciiToUpperCase).collect(Collectors.toSet());
- private static final Index __blackCiphers = new Index.Builder()
- .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 __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));
}
/**
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
index 5bd69a10863..687995cc90d 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
@@ -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)
{
- _expect100Continue = true;
- }
- else if (expect == HttpHeaderValue.PROCESSING)
- {
- _expect102Processing = true;
- }
- else
- {
- 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;
- }
+ case CONTINUE:
+ _expect100Continue = true;
+ return true;
+ case PROCESSING:
+ _expect102Processing = true;
+ return true;
+ default:
+ return false;
}
+ }, s -> false))
+ {
+ _unknownExpectation = true;
+ _expect100Continue = false;
+ _expect102Processing = false;
}
break;
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
index 44c54be7cdc..b4d5e850415 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
@@ -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 _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))
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index 0658e1edc38..a1a3c56253c 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -288,7 +288,7 @@ public class Request implements HttpServletRequest
return !isPush() && getHttpChannel().getHttpTransport().isPushSupported();
}
- private static EnumSet NOT_PUSHED_HEADERS = EnumSet.of(
+ private static final EnumSet 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 vals = getParameters().getValues(name);
if (vals == null)
return null;
- return vals.toArray(new String[vals.size()]);
+ return vals.toArray(new String[0]);
}
public MultiMap getQueryParameters()
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java
index 6ea1cf527c8..99f3176fbfa 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java
@@ -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 implements Index.Mutable
{
- 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 implements Index.Mutable
* utf16
* utf8
*
- * The tree has 10 nodes as follows:
+ * The tree switching by character is:
*
- * 1 - 6
- * /
- * _ - 8
- * /
- * u - t - f - 1 - 6
- * \
- * 8
+ * 1 - 6
+ * /
+ * _ - 8
+ * /
+ * root - u - t - f - 1 - 6
+ * \
+ * 8
*
+ * The count also applies to ternary trees as follows:
+ *
+ * root - u - t - f - _ ----- 1 - 6
+ * \ \
+ * 1 - 6 8
+ * \
+ * 8
+ *
+ * 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 implements Index.Mutable
? 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,45 +136,62 @@ abstract class AbstractTrie implements Index.Mutable
{
int required = 0;
- // Examine all the keys in the subtree
- Character nodeChar = null;
- for (int i = 0; i < length; i++)
+ while (true)
{
- String k = keys.get(offset + i);
+ // Examine all the keys in the subtree
+ Character nodeChar = null;
+ for (int i = 0; i < length; i++)
+ {
+ String k = keys.get(offset + i);
- // If the key is shorter than our current index then ignore it
- if (k.length() <= index)
- continue;
+ // If the key is shorter than our current index then ignore it
+ if (k.length() <= index)
+ continue;
- // Get the character at the index of the current key
- char c = k.charAt(index);
+ // Get the character at the index of the current key
+ char c = k.charAt(index);
- // If the character is the same as the current node, then we are
- // still in the current node and need to continue searching for the
- // next node or the end of the keys
- if (nodeChar != null && c == nodeChar)
- continue;
+ // If the character is the same as the current node, then we are
+ // still in the current node and need to continue searching for the
+ // next node or the end of the keys
+ if (nodeChar != null && c == nodeChar)
+ continue;
- // The character is a new node, so increase required by 1
- required++;
+ // The character is a new node, so increase required by 1
+ required++;
- // if we had a previous node, then add the required nodes for the subtree under it.
+ // if we had a previous node, then add the required nodes for the subtree under it.
+ if (nodeChar != null)
+ required += AbstractTrie.requiredCapacity(keys, offset, i, index + 1);
+
+ // set the char for the new node
+ nodeChar = c;
+
+ // reset the offset, length and index to continue iteration from the start of the new node
+ offset += i;
+ length -= i;
+ i = 0;
+ }
+
+ // If we finish the iteration with a nodeChar, then we must add the required nodes for the subtree under it.
if (nodeChar != null)
- required += AbstractTrie.requiredCapacity(keys, offset, i, index + 1);
+ {
+ // instead of recursion here, we loop to avoid tail recursion
+ index++;
+ continue;
+ }
- // set the char for the new node
- nodeChar = c;
-
- // reset the offset, length and index to continue iteration from the start of the new node
- offset += i;
- length -= i;
- i = 0;
+ return required;
}
+ }
- // 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);
-
- return required;
+ protected boolean putAll(Map contents)
+ {
+ for (Map.Entry entry : contents.entrySet())
+ {
+ if (!put(entry.getKey(), entry.getValue()))
+ return false;
+ }
+ return true;
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
index 55498275e5d..01fe5b22a65 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
@@ -53,6 +53,7 @@ import java.util.Set;
*
* @param the Entry type
*/
+@Deprecated
class ArrayTernaryTrie extends AbstractTrie
{
private static final int LO = 1;
@@ -70,7 +71,7 @@ class ArrayTernaryTrie extends AbstractTrie
* 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 extends AbstractTrie
/**
* 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 extends AbstractTrie
_key = new String[capacity];
}
- @SuppressWarnings("unchecked")
- ArrayTernaryTrie(boolean insensitive, Map 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 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 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 extends AbstractTrie
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 extends AbstractTrie
if (t == _rows)
{
_rows++;
- if (_rows >= _key.length)
+ if (_rows > _key.length)
{
_rows--;
return false;
@@ -202,7 +178,7 @@ class ArrayTernaryTrie extends AbstractTrie
if (t == _rows)
{
_rows++;
- if (_rows >= _key.length)
+ if (_rows > _key.length)
{
_rows--;
return false;
@@ -223,8 +199,8 @@ class ArrayTernaryTrie extends AbstractTrie
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 extends AbstractTrie
{
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 extends AbstractTrie
{
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 extends AbstractTrie
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 extends AbstractTrie
{
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 extends AbstractTrie
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)
{
- buf.append(',');
+ 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 extends AbstractTrie
}
}
- static class Growing extends AbstractTrie
+ @Deprecated
+ public static class Growing extends AbstractTrie
{
private final int _growby;
private ArrayTernaryTrie _trie;
- Growing(boolean insensitive, int capacity, int growby)
+ public Growing(boolean insensitive, int capacity, int growby)
{
super(insensitive);
_growby = growby;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java
index 643a00af579..727dddf6287 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java
@@ -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;
/**
* A Trie String lookup data structure using a fixed size array.
@@ -29,8 +29,8 @@ import java.util.Set;
* indexed in each lookup table, whilst infrequently used characters
* must use a big character table.
*
- * This Trie is very space efficient if the key characters are
- * from ' ', '+', '-', ':', ';', '.', 'A' to 'Z' or 'a' to 'z'.
+ *
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.
*
@@ -45,221 +45,331 @@ import java.util.Set;
*/
class ArrayTrie extends AbstractTrie
{
+ 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 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, 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 The value type of the node.
+ */
+ private static class Node
+ {
+ 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[] _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 The value type of the Trie
+ * @return a Trie containing the contents or null if not possible.
+ */
+ public static ArrayTrie from(int capacity, boolean caseSensitive, Map contents)
+ {
+ // can't do infinite capacity
+ if (capacity < 0)
+ return null;
+
+ if (capacity > MAX_CAPACITY)
+ return null;
+
+ ArrayTrie 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 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 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 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)
- return false;
- char[] big = _bigIndex[t];
- if (big == null)
- big = _bigIndex[t] = new char[128];
- t = big[c];
- if (t == 0)
+ // 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 node = _node[last];
+ if (node != null)
{
- if (_rows == _value.length)
+ char[] big = node._bigRow;
+ if (big != null)
+ {
+ for (int idx = _bigRowSize; idx < big.length; idx += 2)
+ {
+ if (big[idx] == c)
+ {
+ row = big[idx + 1];
+ break;
+ }
+ }
+ }
+ }
+
+ if (row == 0)
+ {
+ // Not found, so we need a new row
+ if (_rows == _node.length - 1)
return false;
- t = big[c] = ++_rows;
+
+ if (node == null)
+ node = _node[last] = new Node<>();
+ char[] big = node._bigRow;
+
+ // Expand the size of the bigRow to have extended lookups
+ if (big == null)
+ big = node._bigRow = new char[_bigRowSize + 2];
+ else
+ big = node._bigRow = Arrays.copyOf(big, Math.max(big.length, _bigRowSize) + 2);
+
+ // set the lookup char and its row
+ // TODO if the extended big row entries were sorted, then missed lookups could be aborted sooner
+ // TODO and/or a binary chop search could be done for hits.
+ big[big.length - 2] = c;
+ row = big[big.length - 1] = ++_rows;
}
}
}
- if (t >= _key.length)
+ // We have processed all characters so set the key and value in the current Node
+ Node node = _node[row];
+ if (node == null)
+ node = _node[row] = new Node<>();
+ node._key = key;
+ node._value = value;
+ return true;
+ }
+
+ private int lookup(int row, char c)
+ {
+ // If the char is small we can lookup in the index table
+ if (c < 0x80)
{
- _rows = (char)_key.length;
- return false;
+ 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 node = _node[row];
+ char[] big = node == null ? null : _node[row]._bigRow;
+ int idx = -column;
+ if (big == null || idx >= big.length)
+ return -1;
+ row = big[idx];
+ }
+ return row == 0 ? -1 : row;
+ }
}
- _key[t] = v == null ? null : s;
- _value[t] = v;
- return true;
+ // Not an indexed char, so do a linear search through he tail of the bigRow
+ Node 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)
- return null;
- }
- else
- {
- char[] big = _bigIndex == null ? null : _bigIndex[t];
- if (big == null)
- return null;
- t = big[c];
- if (t == 0)
- return null;
- }
+ row = lookup(row, c);
+ if (row < 0)
+ return null;
}
- return _value[t];
+ Node 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)
- return null;
- }
- else
- {
- char[] big = _bigIndex == null ? null : _bigIndex[t];
- if (big == null)
- return null;
- t = big[c];
- if (t == 0)
- return null;
- }
+ row = lookup(row, (char)(c & 0xff));
+ if (row < 0)
+ return null;
}
- return (V)_value[t];
+ Node node = _node[row];
+ return node == null ? null : node._value;
}
@Override
@@ -282,177 +392,108 @@ class ArrayTrie extends AbstractTrie
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)
- 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;
- }
+ int next = lookup(row, c);
+ if (next < 0)
+ break;
- // Is the next Trie is a match
- if (_key[t] != null)
+ // Is the row a match?
+ Node 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 node._value;
}
+
+ row = next;
}
- return (V)_value[t];
+ Node node = _node[row];
+ return node == null ? null : node._value;
}
- private V getBest(int t, byte[] b, int offset, int len)
+ private V getBest(int row, byte[] b, int offset, int len)
{
for (int i = 0; i < len; i++)
{
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)
- 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;
- }
+ int next = lookup(row, (char)(c & 0xff));
+ if (next < 0)
+ break;
- // Is the next Trie is a match
- if (_key[t] != null)
+ // Is the next row a match?
+ Node 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 node = _node[row];
+ return node == null ? null : node._value;
}
- private V getBest(int t, ByteBuffer b, int offset, int len)
+ private V getBest(int row, ByteBuffer b, int offset, int len)
{
int pos = b.position() + offset;
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)
- 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;
- }
+ int next = lookup(row, (char)(c & 0xff));
+ if (next < 0)
+ break;
- // Is the next Trie is a match
- if (_key[t] != null)
+ // Is the next row a match?
+ Node 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 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 keySet()
{
- Set 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 extends AbstractTrie
return keySet().isEmpty();
}
- private void keySet(Set set, int t)
+ public void dumpStdErr()
{
- if (t < _value.length && _value[t] != null)
- set.add(_key[t]);
-
- for (int i = 0; i < ROW_SIZE; i++)
+ System.err.print("row:");
+ for (int c = 0; c < ROW_SIZE; c++)
{
- int idx = t * ROW_SIZE + i;
- if (idx < _rowIndex.length && _rowIndex[idx] != 0)
- keySet(set, _rowIndex[idx]);
- }
-
- char[] big = _bigIndex == null || t >= _bigIndex.length ? null : _bigIndex[t];
- if (big != null)
- {
- for (int i : big)
+ for (int i = 0; i < 0x7f; i++)
{
- if (i != 0)
- keySet(set, 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 row = 0; row <= _rows; row++)
+ {
+ System.err.printf("%3x:", row);
+ for (int c = 0; c < ROW_SIZE; c++)
+ {
+ char ch = _table[row * ROW_SIZE + c];
+ if (ch == 0)
+ System.err.print(" .");
+ else
+ System.err.printf("%3x", (int)ch);
+ }
+ Node node = _node[row];
+ if (node != null)
+ {
+ System.err.printf(" : %s%n", node);
+ char[] bigRow = node._bigRow;
+ if (bigRow != null)
+ {
+ System.err.print(" :");
+ for (int c = 0; c < Math.min(_bigRowSize, bigRow.length); c++)
+ {
+ char ch = bigRow[c];
+ if (ch == 0)
+ System.err.print(" _");
+ else
+ System.err.printf("%3x", (int)ch);
+ }
+
+ for (int c = _bigRowSize; c < bigRow.length; c += 2)
+ System.err.printf(" %s>%x", bigRow[c], (int)bigRow[c + 1]);
+
+ System.err.println();
+ }
+ }
+ else
+ System.err.println();
+ }
+ System.err.println();
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/EmptyTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/EmptyTrie.java
index 65b1aab204e..0ccfaeb0e34 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/EmptyTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/EmptyTrie.java
@@ -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 the entry type
*/
class EmptyTrie extends AbstractTrie
{
@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 EmptyTrie instance(boolean caseSensitive)
@@ -34,15 +42,21 @@ class EmptyTrie extends AbstractTrie
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 extends AbstractTrie
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 extends AbstractTrie
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 extends AbstractTrie
}
@Override
- public void clear()
+ protected boolean putAll(Map contents)
{
+ return false;
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Index.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Index.java
index 3a9e02da83a..5a12e7777b9 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Index.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Index.java
@@ -35,7 +35,7 @@ public interface Index
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 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 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 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 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
/**
* 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,46 +212,61 @@ public interface Index
return this;
}
+ /**
+ * Configure the index to be mutable.
+ *
+ * @return a {@link Mutable.Builder} configured like this builder.
+ */
+ public Mutable.Builder mutable()
+ {
+ return this;
+ }
+
/**
* Build a {@link Mutable} instance.
* @return a {@link Mutable} instance.
*/
public Mutable 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 result;
- if (maxCapacity > 0)
- {
- result = new ArrayTernaryTrie<>(!caseSensitive, maxCapacity);
- }
- else if (maxCapacity < 0)
- {
- if (caseSensitive)
- result = new ArrayTernaryTrie.Growing<>(false, 512, 512);
- else
- result = new TreeTrie<>();
- }
- else
- {
- result = EmptyTrie.instance(caseSensitive);
- }
+ // Work out needed capacity
+ int capacity = (contents == null) ? 0 : AbstractTrie.requiredCapacity(contents.keySet(), caseSensitive);
- if (contents != null)
- {
- for (Map.Entry entry : contents.entrySet())
- {
- if (!result.put(entry.getKey(), entry.getValue()))
- throw new AssertionError("Index capacity exceeded at " + entry.getKey());
- }
- }
- return result;
+ // check capacities
+ if (maxCapacity >= 0 && capacity > maxCapacity)
+ throw new IllegalStateException("Insufficient maxCapacity for contents");
+
+ // try all the tries
+ AbstractTrie 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);
}
}
}
+ /**
+ * A special purpose static builder for fast creation of specific Index type
+ * @param maxCapacity The max capacity of the index
+ * @param The type of the index
+ * @return A case sensitive mutable Index tacking visible ASCII alphabet to a max capacity.
+ */
+ static Mutable buildCaseSensitiveMutableVisibleAsciiAlphabet(int maxCapacity)
+ {
+ if (maxCapacity < 0 || maxCapacity > ArrayTrie.MAX_CAPACITY)
+ return new TreeTrie<>(true);
+ if (maxCapacity == 0)
+ return EmptyTrie.instance(true);
+ return new ArrayTrie<>(true, maxCapacity);
+ }
+
/**
* Builder of {@link Index} instances.
* @param the entry type
@@ -242,7 +281,8 @@ public interface Index
*/
public Builder()
{
- this(false, null);
+ this.caseSensitive = false;
+ this.contents = null;
}
Builder(boolean caseSensitive, Map contents)
@@ -349,11 +389,22 @@ public interface Index
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 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);
}
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
index 148c30b4805..2b6777dc3b9 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
@@ -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;
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
index abecb46ce1f..e443e67cdfe 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
@@ -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,9 +40,9 @@ import java.util.Set;
*/
class TreeTrie extends AbstractTrie
{
- private static final int[] LOOKUP =
+ private static final int[] LOOKUP_INSENSITIVE =
{
- // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
/*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/*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,
@@ -51,57 +52,95 @@ class TreeTrie extends AbstractTrie
/*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[] _nextIndex;
- private final List> _nextOther = new ArrayList<>();
- private final char _c;
- private String _key;
- private V _value;
+ /** Create a trie from capacity and content
+ * @param caseSensitive True if the Trie keys are case sensitive
+ * @param contents The known contents of the Trie
+ * @param The value type of the Trie
+ * @return a Trie containing the contents or null if not possible.
+ */
+ public static AbstractTrie from(boolean caseSensitive, Map contents)
+ {
+ TreeTrie trie = new TreeTrie<>(caseSensitive);
+ if (contents != null && !trie.putAll(contents))
+ return null;
+ return trie;
+ }
+
+ private static class Node
+ {
+ private final Node[] _nextIndex;
+ private final List> _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 _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((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 t = this;
+ Node 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(c);
+ t._nextIndex[index] = new Node(c);
t = t._nextIndex[index];
}
else
{
- TreeTrie n = null;
+ Node n = null;
for (int i = t._nextOther.size(); i-- > 0; )
{
n = t._nextOther.get(i);
@@ -111,7 +150,7 @@ class TreeTrie extends AbstractTrie
}
if (n == null)
{
- n = new TreeTrie(c);
+ n = new Node(c);
t._nextOther.add(n);
}
t = n;
@@ -125,11 +164,11 @@ class TreeTrie extends AbstractTrie
@Override
public V get(String s, int offset, int len)
{
- TreeTrie t = this;
+ Node 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 extends AbstractTrie
}
else
{
- TreeTrie n = null;
+ Node n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@@ -157,11 +196,11 @@ class TreeTrie extends AbstractTrie
@Override
public V get(ByteBuffer b, int offset, int len)
{
- TreeTrie t = this;
+ Node 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 extends AbstractTrie
}
else
{
- TreeTrie n = null;
+ Node n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@@ -189,11 +228,15 @@ class TreeTrie extends AbstractTrie
@Override
public V getBest(byte[] b, int offset, int len)
{
- TreeTrie t = this;
+ return getBest(_root, b, offset, len);
+ }
+
+ private V getBest(Node 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 extends AbstractTrie
}
else
{
- TreeTrie n = null;
+ Node n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@@ -219,7 +262,7 @@ class TreeTrie extends AbstractTrie
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 extends AbstractTrie
@Override
public V getBest(String s, int offset, int len)
{
- TreeTrie t = this;
+ return getBest(_root, s, offset, len);
+ }
+
+ private V getBest(Node 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 extends AbstractTrie
}
else
{
- TreeTrie n = null;
+ Node n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@@ -273,7 +320,7 @@ class TreeTrie extends AbstractTrie
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 extends AbstractTrie
{
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 t, ByteBuffer b, int offset, int len)
{
- TreeTrie 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 extends AbstractTrie
}
else
{
- TreeTrie n = null;
+ Node n = null;
for (int j = t._nextOther.size(); j-- > 0; )
{
n = t._nextOther.get(j);
@@ -323,7 +369,7 @@ class TreeTrie extends AbstractTrie
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,44 +382,63 @@ class TreeTrie extends AbstractTrie
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 void toString(Appendable out, TreeTrie t)
+ private static void toString(Appendable out, Node t, String separator)
{
- if (t != null)
+ loop: while (true)
{
- if (t._value != null)
+ if (t != null)
{
- try
+ if (t._value != null)
{
- out.append(',');
- out.append(t._key);
- out.append('=');
- out.append(t._value.toString());
+ try
+ {
+ out.append(separator);
+ separator = ",";
+ out.append(t._key);
+ out.append('=');
+ out.append(t._value.toString());
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
}
- catch (IOException e)
+
+ for (int i = 0; i < INDEX;)
{
- throw new RuntimeException(e);
+ Node n = t._nextIndex[i++];
+ if (n != null)
+ {
+ // can we avoid tail recurse?
+ if (i == INDEX && t._nextOther.size() == 0)
+ {
+ t = n;
+ continue loop;
+ }
+ // recurse
+ toString(out, n, separator);
+ }
+ }
+ for (int i = t._nextOther.size(); i-- > 0; )
+ {
+ // can we avoid tail recurse?
+ if (i == 0)
+ {
+ t = t._nextOther.get(i);
+ continue loop;
+ }
+ toString(out, t._nextOther.get(i), separator);
}
}
- for (int i = 0; i < INDEX; i++)
- {
- if (t._nextIndex[i] != null)
- toString(out, t._nextIndex[i]);
- }
- for (int i = t._nextOther.size(); i-- > 0; )
- {
- toString(out, t._nextOther.get(i));
- }
+ break;
}
}
@@ -381,11 +446,11 @@ class TreeTrie extends AbstractTrie
public Set keySet()
{
Set keys = new HashSet<>();
- keySet(keys, this);
+ keySet(keys, _root);
return keys;
}
- private static void keySet(Set set, TreeTrie t)
+ private static void keySet(Set set, Node t)
{
if (t != null)
{
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/IndexTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/IndexTest.java
index a8ff8b3b5b5..c18bb1a746f 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/IndexTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/IndexTest.java
@@ -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().build(), instanceOf(EmptyTrie.class));
- Index.Builder builder = new Index.Builder<>();
- builder.caseSensitive(true);
- for (int i = 0; i < size; i++)
- {
- builder.with("/test/group" + i, i);
- }
- Index index = builder.build();
+ // index of ascii characters
+ assertThat(new Index.Builder().caseSensitive(false).with("name", "value").build(), instanceOf(ArrayTrie.class));
+ assertThat(new Index.Builder().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().caseSensitive(false).with(hugekey, "value").build() instanceof TreeTrie);
+ assertTrue(new Index.Builder().caseSensitive(true).with(hugekey, "value").build() instanceof TreeTrie);
}
@Test
- public void overMaxCapacityTest()
+ public void testUnlimitdMutableTrieSelection()
{
- int size = 11_000;
+ assertThat(new Index.Builder().mutable().build(), instanceOf(TreeTrie.class));
+ }
- Index.Builder builder = new Index.Builder<>();
- builder.caseSensitive(true);
- for (int i = 0; i < size; i++)
- {
- builder.with("/test/group" + i, i);
- }
-
- assertThrows(IllegalArgumentException.class, builder::build);
+ @Test
+ public void testLimitedMutableTrieSelection()
+ {
+ assertThat(new Index.Builder().mutable().maxCapacity(500).build(), instanceOf(ArrayTrie.class));
+ assertThat(new Index.Builder().mutable().maxCapacity(Character.MAX_VALUE + 1).build(), instanceOf(TreeTrie.class));
}
}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
index fa1ba2aa3b8..5474f5b4b43 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
@@ -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 implementations()
{
List> impls = new ArrayList<>();
- impls.add(new ArrayTrie(128));
- impls.add(new ArrayTernaryTrie(true, 128));
- impls.add(new ArrayTernaryTrie.Growing(true, 128, 128));
+ for (boolean caseSensitive : new boolean[] {true, false})
+ {
+ impls.add(new ArrayTrie(caseSensitive,128));
+ impls.add(new ArrayTernaryTrie(caseSensitive, 128));
+ impls.add(new TreeTrie<>(caseSensitive));
+ }
for (AbstractTrie 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 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 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 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 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 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 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 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 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 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 trie) throws Exception
+ {
+ assertThrows(NullPointerException.class, () -> trie.put(null, -1));
+ }
+
+ @ParameterizedTest
+ @MethodSource("implementations")
+ public void testNullValue(AbstractTrie 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 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 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(Character.MAX_VALUE + 1));
}
}
diff --git a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/TrieBenchmark.java b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/TrieBenchmark.java
new file mode 100644
index 00000000000..a56859c9e0f
--- /dev/null
+++ b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/TrieBenchmark.java
@@ -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 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 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
+ {
+ Map _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 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();
+ }
+
+}