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:
Scott Mitchell 2015-10-01 18:27:48 -07:00
parent 06c3ae07a0
commit d4680c55d8
3 changed files with 140 additions and 45 deletions

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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);
}
}
}
}