AsciiString contains utility methods
Motivation: When dealing with case insensitive headers it can be useful to have a case insensitive contains method for CharSequence. Modifications: - Add containsCaseInsensative to AsciiString Result: More expressive utility method for case insensitive CharSequence.
This commit is contained in:
parent
06c3ae07a0
commit
d4680c55d8
@ -264,9 +264,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
if (string.getClass() == AsciiString.class) {
|
||||
AsciiString rhs = (AsciiString) string;
|
||||
for (int i = arrayOffset(), j = rhs.arrayOffset(); i < length(); ++i, ++j) {
|
||||
byte c1 = value[i];
|
||||
byte c2 = value[j];
|
||||
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
|
||||
if (!equalsIgnoreCase(value[i], value[j])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -274,9 +272,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
}
|
||||
|
||||
for (int i = arrayOffset(), j = 0; i < length(); ++i, ++j) {
|
||||
char c1 = (char) (value[i] & 0xFF);
|
||||
char c2 = string.charAt(j);
|
||||
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
|
||||
if (!equalsIgnoreCase((char) (value[i] & 0xFF), string.charAt(j))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -553,9 +549,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
thisStart += arrayOffset();
|
||||
final int thisEnd = thisStart + length;
|
||||
while (thisStart < thisEnd) {
|
||||
char c1 = (char) (value[thisStart++] & 0xFF);
|
||||
char c2 = string.charAt(start++);
|
||||
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
|
||||
if (!equalsIgnoreCase((char) (value[thisStart++] & 0xFF), string.charAt(start++))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -756,13 +750,6 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return toAsciiStringArray(Pattern.compile(expr).split(this, max));
|
||||
}
|
||||
|
||||
private static byte c2b(char c) {
|
||||
if (c > MAX_CHAR_VALUE) {
|
||||
return '?';
|
||||
}
|
||||
return (byte) c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the specified {@link String} with the specified delimiter..
|
||||
*/
|
||||
@ -855,6 +842,20 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if {@code a} contains {@code b} in a case sensitive manner.
|
||||
*/
|
||||
public static boolean contains(CharSequence a, CharSequence b) {
|
||||
return contains(a, b, DefaultCharEqualityComparator.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if {@code a} contains {@code b} in a case insensitive manner.
|
||||
*/
|
||||
public static boolean containsIgnoreCase(CharSequence a, CharSequence b) {
|
||||
return contains(a, b, CaseInsensativeCharEqualityComparator.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if both {@link CharSequence}'s are equals when ignore the case. This only supports 8-bit
|
||||
* ASCII.
|
||||
@ -879,9 +880,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return false;
|
||||
}
|
||||
for (int i = 0, j = 0; i < a.length(); ++i, ++j) {
|
||||
char c1 = a.charAt(i);
|
||||
char c2 = b.charAt(j);
|
||||
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
|
||||
if (!equalsIgnoreCase(a.charAt(i), b.charAt(j))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -976,6 +975,62 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return indexOf(cs) >= 0;
|
||||
}
|
||||
|
||||
private interface CharEqualityComparator {
|
||||
boolean equals(char a, char b);
|
||||
}
|
||||
|
||||
private static final class DefaultCharEqualityComparator implements CharEqualityComparator {
|
||||
static final DefaultCharEqualityComparator INSTANCE = new DefaultCharEqualityComparator();
|
||||
private DefaultCharEqualityComparator() { }
|
||||
|
||||
@Override
|
||||
public boolean equals(char a, char b) {
|
||||
return a == b;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CaseInsensativeCharEqualityComparator implements CharEqualityComparator {
|
||||
static final CaseInsensativeCharEqualityComparator INSTANCE = new CaseInsensativeCharEqualityComparator();
|
||||
private CaseInsensativeCharEqualityComparator() { }
|
||||
|
||||
@Override
|
||||
public boolean equals(char a, char b) {
|
||||
return equalsIgnoreCase(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean contains(CharSequence a, CharSequence b, CharEqualityComparator cmp) {
|
||||
if (a == null || b == null || a.length() < b.length()) {
|
||||
return false;
|
||||
}
|
||||
if (b.length() == 0) {
|
||||
return true;
|
||||
}
|
||||
int bStart = 0;
|
||||
for (int i = 0; i < a.length(); ++i) {
|
||||
if (cmp.equals(b.charAt(bStart), a.charAt(i))) {
|
||||
// If b is consumed then true.
|
||||
if (++bStart == b.length()) {
|
||||
return true;
|
||||
}
|
||||
} else if (a.length() - i < b.length()) {
|
||||
// If there are not enough characters left in a for b to be contained, then false.
|
||||
return false;
|
||||
} else {
|
||||
bStart = 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean equalsIgnoreCase(byte a, byte b) {
|
||||
return a == b || toLowerCase(a) == toLowerCase(b);
|
||||
}
|
||||
|
||||
private static boolean equalsIgnoreCase(char a, char b) {
|
||||
return a == b || toLowerCase(a) == toLowerCase(b);
|
||||
}
|
||||
|
||||
private static byte toLowerCase(byte b) {
|
||||
if ('A' <= b && b <= 'Z') {
|
||||
return (byte) (b + 32);
|
||||
@ -996,4 +1051,11 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private static byte c2b(char c) {
|
||||
if (c > MAX_CHAR_VALUE) {
|
||||
return '?';
|
||||
}
|
||||
return (byte) c;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static io.netty.util.AsciiString.caseInsensitiveHashCode;
|
||||
import static io.netty.util.AsciiString.contains;
|
||||
import static io.netty.util.AsciiString.containsIgnoreCase;
|
||||
|
||||
/**
|
||||
* Test for the {@link AsciiString} class
|
||||
@ -99,6 +101,59 @@ public class AsciiStringTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContains() {
|
||||
String[] falseLhs = {null, "a", "aa", "aaa" };
|
||||
String[] falseRhs = {null, "b", "ba", "baa" };
|
||||
for (int i = 0; i < falseLhs.length; ++i) {
|
||||
for (int j = 0; j < falseRhs.length; ++j) {
|
||||
assertContains(falseLhs[i], falseRhs[i], false, false);
|
||||
}
|
||||
}
|
||||
|
||||
assertContains("", "", true, true);
|
||||
assertContains("AsfdsF", "", true, true);
|
||||
assertContains("", "b", false, false);
|
||||
assertContains("a", "a", true, true);
|
||||
assertContains("a", "b", false, false);
|
||||
assertContains("a", "A", false, true);
|
||||
String b = "xyz";
|
||||
String a = b;
|
||||
assertContains(a, b, true, true);
|
||||
|
||||
a = "a" + b;
|
||||
assertContains(a, b, true, true);
|
||||
|
||||
a = b + "a";
|
||||
assertContains(a, b, true, true);
|
||||
|
||||
a = "a" + b + "a";
|
||||
assertContains(a, b, true, true);
|
||||
|
||||
b = "xYz";
|
||||
a = "xyz";
|
||||
assertContains(a, b, false, true);
|
||||
|
||||
b = "xYz";
|
||||
a = "xyzxxxXyZ" + b + "aaa";
|
||||
assertContains(a, b, true, true);
|
||||
|
||||
b = "foOo";
|
||||
a = "fooofoO";
|
||||
assertContains(a, b, false, true);
|
||||
|
||||
b = "Content-Equals: 10000";
|
||||
a = "content-equals: 1000";
|
||||
assertContains(a, b, false, false);
|
||||
a += "0";
|
||||
assertContains(a, b, false, true);
|
||||
}
|
||||
|
||||
private static void assertContains(String a, String b, boolean caseSensativeEquals, boolean caseInsenstaiveEquals) {
|
||||
assertEquals(caseSensativeEquals, contains(a, b));
|
||||
assertEquals(caseInsenstaiveEquals, containsIgnoreCase(a, b));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaseSensitivity() {
|
||||
Random r = new Random();
|
||||
|
@ -17,7 +17,6 @@ package io.netty.microbench.headers;
|
||||
|
||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
||||
import io.netty.handler.codec.http2.Http2Headers;
|
||||
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.ByteString;
|
||||
@ -44,7 +43,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@Threads(1)
|
||||
@State(Scope.Benchmark)
|
||||
@Warmup(iterations = 5)
|
||||
@Measurement(iterations = 10)
|
||||
@Measurement(iterations = 5)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
public class HeadersBenchmark extends AbstractMicrobenchmark {
|
||||
|
||||
@ -68,7 +67,7 @@ public class HeadersBenchmark extends AbstractMicrobenchmark {
|
||||
http2Names = new ByteString[headers.size()];
|
||||
http2Values = new ByteString[headers.size()];
|
||||
httpHeaders = new DefaultHttpHeaders(false);
|
||||
http2Headers = new DefaultHttp2Headers();
|
||||
http2Headers = new DefaultHttp2Headers(false);
|
||||
int idx = 0;
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
String name = header.getKey();
|
||||
@ -137,7 +136,7 @@ public class HeadersBenchmark extends AbstractMicrobenchmark {
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public DefaultHttp2Headers http2Put() {
|
||||
DefaultHttp2Headers headers = new DefaultHttp2Headers();
|
||||
DefaultHttp2Headers headers = new DefaultHttp2Headers(false);
|
||||
for (int i = 0; i < httpNames.length; i++) {
|
||||
headers.add(httpNames[i], httpValues[i]);
|
||||
}
|
||||
@ -146,30 +145,9 @@ public class HeadersBenchmark extends AbstractMicrobenchmark {
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void http2IterateNew(Blackhole bh) {
|
||||
public void http2Iterate(Blackhole bh) {
|
||||
for (Entry<ByteString, ByteString> entry : http2Headers) {
|
||||
bh.consume(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void http2IterateOld(Blackhole bh) {
|
||||
// This is how we had to iterate in the Http2HeadersEncoder when writing the frames on the wire
|
||||
// in order to ensure that reserved headers come first.
|
||||
for (Http2Headers.PseudoHeaderName pseudoHeader : Http2Headers.PseudoHeaderName.values()) {
|
||||
ByteString name = pseudoHeader.value();
|
||||
ByteString value = http2Headers.get(name);
|
||||
if (value != null) {
|
||||
bh.consume(value);
|
||||
}
|
||||
}
|
||||
for (Entry<ByteString, ByteString> entry : http2Headers) {
|
||||
final ByteString name = entry.getKey();
|
||||
final ByteString value = entry.getValue();
|
||||
if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) {
|
||||
bh.consume(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user