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) {
|
if (string.getClass() == AsciiString.class) {
|
||||||
AsciiString rhs = (AsciiString) string;
|
AsciiString rhs = (AsciiString) string;
|
||||||
for (int i = arrayOffset(), j = rhs.arrayOffset(); i < length(); ++i, ++j) {
|
for (int i = arrayOffset(), j = rhs.arrayOffset(); i < length(); ++i, ++j) {
|
||||||
byte c1 = value[i];
|
if (!equalsIgnoreCase(value[i], value[j])) {
|
||||||
byte c2 = value[j];
|
|
||||||
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
|
|
||||||
return false;
|
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) {
|
for (int i = arrayOffset(), j = 0; i < length(); ++i, ++j) {
|
||||||
char c1 = (char) (value[i] & 0xFF);
|
if (!equalsIgnoreCase((char) (value[i] & 0xFF), string.charAt(j))) {
|
||||||
char c2 = string.charAt(j);
|
|
||||||
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -553,9 +549,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
|||||||
thisStart += arrayOffset();
|
thisStart += arrayOffset();
|
||||||
final int thisEnd = thisStart + length;
|
final int thisEnd = thisStart + length;
|
||||||
while (thisStart < thisEnd) {
|
while (thisStart < thisEnd) {
|
||||||
char c1 = (char) (value[thisStart++] & 0xFF);
|
if (!equalsIgnoreCase((char) (value[thisStart++] & 0xFF), string.charAt(start++))) {
|
||||||
char c2 = string.charAt(start++);
|
|
||||||
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -756,13 +750,6 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
|||||||
return toAsciiStringArray(Pattern.compile(expr).split(this, max));
|
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..
|
* Splits the specified {@link String} with the specified delimiter..
|
||||||
*/
|
*/
|
||||||
@ -855,6 +842,20 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
|||||||
return hash;
|
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
|
* Returns {@code true} if both {@link CharSequence}'s are equals when ignore the case. This only supports 8-bit
|
||||||
* ASCII.
|
* ASCII.
|
||||||
@ -879,9 +880,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (int i = 0, j = 0; i < a.length(); ++i, ++j) {
|
for (int i = 0, j = 0; i < a.length(); ++i, ++j) {
|
||||||
char c1 = a.charAt(i);
|
if (!equalsIgnoreCase(a.charAt(i), b.charAt(j))) {
|
||||||
char c2 = b.charAt(j);
|
|
||||||
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -976,6 +975,62 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
|||||||
return indexOf(cs) >= 0;
|
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) {
|
private static byte toLowerCase(byte b) {
|
||||||
if ('A' <= b && b <= 'Z') {
|
if ('A' <= b && b <= 'Z') {
|
||||||
return (byte) (b + 32);
|
return (byte) (b + 32);
|
||||||
@ -996,4 +1051,11 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
|||||||
}
|
}
|
||||||
return b;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.assertNotEquals;
|
||||||
import static io.netty.util.AsciiString.caseInsensitiveHashCode;
|
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
|
* 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
|
@Test
|
||||||
public void testCaseSensitivity() {
|
public void testCaseSensitivity() {
|
||||||
Random r = new Random();
|
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.http.DefaultHttpHeaders;
|
||||||
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
||||||
import io.netty.handler.codec.http2.Http2Headers;
|
|
||||||
import io.netty.microbench.util.AbstractMicrobenchmark;
|
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
import io.netty.util.ByteString;
|
import io.netty.util.ByteString;
|
||||||
@ -44,7 +43,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
@Threads(1)
|
@Threads(1)
|
||||||
@State(Scope.Benchmark)
|
@State(Scope.Benchmark)
|
||||||
@Warmup(iterations = 5)
|
@Warmup(iterations = 5)
|
||||||
@Measurement(iterations = 10)
|
@Measurement(iterations = 5)
|
||||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
public class HeadersBenchmark extends AbstractMicrobenchmark {
|
public class HeadersBenchmark extends AbstractMicrobenchmark {
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ public class HeadersBenchmark extends AbstractMicrobenchmark {
|
|||||||
http2Names = new ByteString[headers.size()];
|
http2Names = new ByteString[headers.size()];
|
||||||
http2Values = new ByteString[headers.size()];
|
http2Values = new ByteString[headers.size()];
|
||||||
httpHeaders = new DefaultHttpHeaders(false);
|
httpHeaders = new DefaultHttpHeaders(false);
|
||||||
http2Headers = new DefaultHttp2Headers();
|
http2Headers = new DefaultHttp2Headers(false);
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||||
String name = header.getKey();
|
String name = header.getKey();
|
||||||
@ -137,7 +136,7 @@ public class HeadersBenchmark extends AbstractMicrobenchmark {
|
|||||||
@Benchmark
|
@Benchmark
|
||||||
@BenchmarkMode(Mode.AverageTime)
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
public DefaultHttp2Headers http2Put() {
|
public DefaultHttp2Headers http2Put() {
|
||||||
DefaultHttp2Headers headers = new DefaultHttp2Headers();
|
DefaultHttp2Headers headers = new DefaultHttp2Headers(false);
|
||||||
for (int i = 0; i < httpNames.length; i++) {
|
for (int i = 0; i < httpNames.length; i++) {
|
||||||
headers.add(httpNames[i], httpValues[i]);
|
headers.add(httpNames[i], httpValues[i]);
|
||||||
}
|
}
|
||||||
@ -146,30 +145,9 @@ public class HeadersBenchmark extends AbstractMicrobenchmark {
|
|||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@BenchmarkMode(Mode.AverageTime)
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
public void http2IterateNew(Blackhole bh) {
|
public void http2Iterate(Blackhole bh) {
|
||||||
for (Entry<ByteString, ByteString> entry : http2Headers) {
|
for (Entry<ByteString, ByteString> entry : http2Headers) {
|
||||||
bh.consume(entry);
|
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…
x
Reference in New Issue
Block a user