NetUtil valid IP methods to accept CharSequence (#7827)

* NetUtil valid IP methods to accept CharSequence

Motivation:
NetUtil has methods to determine if a String is a valid IP address. These methods don't rely upon String specific methods and can use CharSequence instead.

Modifications:
- Use CharSequence instead of String for the IP validator methods.
- Avoid object allocation in AsciiString#indexOf(char,int) and reduce
byte code

Result:
No more copy operation required if a CharSequence exists.
This commit is contained in:
Scott Mitchell 2018-03-31 23:39:43 -07:00 committed by Norman Maurer
parent 9d51a40df0
commit 602ee5444d
3 changed files with 172 additions and 87 deletions

View File

@ -15,7 +15,6 @@
*/
package io.netty.util;
import io.netty.util.ByteProcessor.IndexOfProcessor;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.InternalThreadLocalMap;
import io.netty.util.internal.PlatformDependent;
@ -673,44 +672,35 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
* @throws NullPointerException if {@code subString} is {@code null}.
*/
public int indexOf(CharSequence subString, int start) {
final int subCount = subString.length();
if (start < 0) {
start = 0;
}
final int thisLen = length();
int subCount = subString.length();
if (subCount <= 0) {
return start < thisLen ? start : thisLen;
return start < length ? start : length;
}
if (subCount > thisLen - start) {
return -1;
if (subCount > length - start) {
return INDEX_NOT_FOUND;
}
final char firstChar = subString.charAt(0);
if (firstChar > MAX_CHAR_VALUE) {
return -1;
return INDEX_NOT_FOUND;
}
ByteProcessor IndexOfVisitor = new IndexOfProcessor((byte) firstChar);
try {
for (;;) {
int i = forEachByte(start, thisLen - start, IndexOfVisitor);
if (i == -1 || subCount + i > thisLen) {
return -1; // handles subCount > count || start >= count
}
final byte firstCharAsByte = c2b0(firstChar);
final int len = offset + start + length - subCount;
for (int i = start + offset; i <= len; ++i) {
if (value[i] == firstCharAsByte) {
int o1 = i, o2 = 0;
while (++o2 < subCount && b2c(value[++o1 + arrayOffset()]) == subString.charAt(o2)) {
while (++o2 < subCount && b2c(value[++o1]) == subString.charAt(o2)) {
// Intentionally empty
}
if (o2 == subCount) {
return i;
return i - offset;
}
start = i + 1;
}
} catch (Exception e) {
PlatformDependent.throwException(e);
return -1;
}
return INDEX_NOT_FOUND;
}
/**
@ -723,22 +713,22 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
* -1 if found no occurrence.
*/
public int indexOf(char ch, int start) {
if (ch > MAX_CHAR_VALUE) {
return INDEX_NOT_FOUND;
}
if (start < 0) {
start = 0;
}
final int thisLen = length();
if (ch > MAX_CHAR_VALUE) {
return -1;
}
try {
return forEachByte(start, thisLen - start, new IndexOfProcessor((byte) ch));
} catch (Exception e) {
PlatformDependent.throwException(e);
return -1;
final byte chAsByte = c2b0(ch);
final int len = offset + start + length;
for (int i = start + offset; i < len; ++i) {
if (value[i] == chAsByte) {
return i - offset;
}
}
return INDEX_NOT_FOUND;
}
/**
@ -766,44 +756,35 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
* @throws NullPointerException if {@code subString} is {@code null}.
*/
public int lastIndexOf(CharSequence subString, int start) {
final int thisLen = length();
final int subCount = subString.length();
if (subCount > thisLen || start < 0) {
return -1;
if (start < 0) {
start = 0;
}
if (subCount <= 0) {
return start < thisLen ? start : thisLen;
return start < length ? start : length;
}
if (subCount > length - start) {
return INDEX_NOT_FOUND;
}
start = Math.min(start, thisLen - subCount);
// count and subCount are both >= 1
final char firstChar = subString.charAt(0);
if (firstChar > MAX_CHAR_VALUE) {
return -1;
return INDEX_NOT_FOUND;
}
ByteProcessor IndexOfVisitor = new IndexOfProcessor((byte) firstChar);
try {
for (;;) {
int i = forEachByteDesc(start, thisLen - start, IndexOfVisitor);
if (i == -1) {
return -1;
}
final byte firstCharAsByte = c2b0(firstChar);
final int end = offset + start;
for (int i = offset + start + length - subCount; i >= end; --i) {
if (value[i] == firstCharAsByte) {
int o1 = i, o2 = 0;
while (++o2 < subCount && b2c(value[++o1 + arrayOffset()]) == subString.charAt(o2)) {
while (++o2 < subCount && b2c(value[++o1]) == subString.charAt(o2)) {
// Intentionally empty
}
if (o2 == subCount) {
return i;
return i - offset;
}
start = i - 1;
}
} catch (Exception e) {
PlatformDependent.throwException(e);
return -1;
}
return INDEX_NOT_FOUND;
}
/**
@ -895,29 +876,23 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
return this;
}
final int index;
final byte oldCharByte = c2b(oldChar);
try {
index = forEachByte(new IndexOfProcessor(oldCharByte));
} catch (Exception e) {
PlatformDependent.throwException(e);
return this;
}
if (index == -1) {
return this;
}
final byte newCharByte = c2b(newChar);
byte[] buffer = new byte[length()];
for (int i = 0, j = arrayOffset(); i < buffer.length; i++, j++) {
byte b = value[j];
if (b == oldCharByte) {
b = newCharByte;
final byte oldCharAsByte = c2b0(oldChar);
final byte newCharAsByte = c2b(newChar);
final int len = offset + length;
for (int i = offset; i < len; ++i) {
if (value[i] == oldCharAsByte) {
byte[] buffer = new byte[length()];
System.arraycopy(value, offset, buffer, 0, i - offset);
buffer[i - offset] = newCharAsByte;
++i;
for (; i < len; ++i) {
byte oldValue = value[i];
buffer[i - offset] = oldValue != oldCharAsByte ? oldValue : newCharAsByte;
}
return new AsciiString(buffer, false);
}
buffer[i] = b;
}
return new AsciiString(buffer, false);
return this;
}
/**
@ -1832,10 +1807,7 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
return INDEX_NOT_FOUND;
}
final int sz = cs.length();
if (start < 0) {
start = 0;
}
for (int i = start; i < sz; i++) {
for (int i = start < 0 ? 0 : start; i < sz; i++) {
if (cs.charAt(i) == searchChar) {
return i;
}
@ -1879,6 +1851,10 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
return (byte) ((c > MAX_CHAR_VALUE) ? '?' : c);
}
private static byte c2b0(char c) {
return (byte) c;
}
public static char b2c(byte b) {
return (char) (b & 0xFF);
}

View File

@ -41,6 +41,8 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import static io.netty.util.AsciiString.indexOf;
/**
* A class that holds a number of network-related constants.
* <p/>
@ -462,6 +464,10 @@ public final class NetUtil {
}
public static boolean isValidIpV6Address(String ip) {
return isValidIpV6Address((CharSequence) ip);
}
public static boolean isValidIpV6Address(CharSequence ip) {
int end = ip.length();
if (end < 2) {
return false;
@ -557,7 +563,7 @@ public final class NetUtil {
}
// 7 - is minimum IPv4 address length
int ipv4End = ip.indexOf('%', ipv4Start + 7);
int ipv4End = indexOf(ip, '%', ipv4Start + 7);
if (ipv4End < 0) {
ipv4End = end;
}
@ -623,7 +629,17 @@ public final class NetUtil {
}
/**
* Takes a string and parses it to see if it is a valid IPV4 address.
* Takes a {@link CharSequence} and parses it to see if it is a valid IPV4 address.
*
* @return true, if the string represents an IPV4 address in dotted
* notation, false otherwise
*/
public static boolean isValidIpV4Address(CharSequence ip) {
return isValidIpV4Address(ip, 0, ip.length());
}
/**
* Takes a {@link String} and parses it to see if it is a valid IPV4 address.
*
* @return true, if the string represents an IPV4 address in dotted
* notation, false otherwise
@ -632,15 +648,43 @@ public final class NetUtil {
return isValidIpV4Address(ip, 0, ip.length());
}
private static boolean isValidIpV4Address(CharSequence ip, int from, int toExcluded) {
return ip instanceof String ? isValidIpV4Address((String) ip, from, toExcluded) :
ip instanceof AsciiString ? isValidIpV4Address((AsciiString) ip, from, toExcluded) :
isValidIpV4Address0(ip, from, toExcluded);
}
@SuppressWarnings("DuplicateBooleanBranch")
private static boolean isValidIpV4Address(String ip, int from, int toExcluded) {
int len = toExcluded - from;
int i;
return len <= 15 && len >= 7 &&
(i = ip.indexOf('.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) &&
(i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
(i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
isValidIpV4Word(ip, i + 1, toExcluded);
(i = ip.indexOf('.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) &&
(i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
(i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
isValidIpV4Word(ip, i + 1, toExcluded);
}
@SuppressWarnings("DuplicateBooleanBranch")
private static boolean isValidIpV4Address(AsciiString ip, int from, int toExcluded) {
int len = toExcluded - from;
int i;
return len <= 15 && len >= 7 &&
(i = ip.indexOf('.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) &&
(i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
(i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
isValidIpV4Word(ip, i + 1, toExcluded);
}
@SuppressWarnings("DuplicateBooleanBranch")
private static boolean isValidIpV4Address0(CharSequence ip, int from, int toExcluded) {
int len = toExcluded - from;
int i;
return len <= 15 && len >= 7 &&
(i = indexOf(ip, '.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) &&
(i = indexOf(ip, '.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
(i = indexOf(ip, '.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
isValidIpV4Word(ip, i + 1, toExcluded);
}
/**

View File

@ -28,6 +28,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@ -301,6 +302,34 @@ public class AsciiStringCharacterTest {
assertEquals(1, AsciiString.of("aabaabaa").indexOf('a', 1));
assertEquals(3, AsciiString.of("aabaabaa").indexOf('a', 2));
assertEquals(3, AsciiString.of("aabdabaa").indexOf('d', 1));
assertEquals(1, new AsciiString("abcd", 1, 2).indexOf('c', 0));
assertEquals(2, new AsciiString("abcd", 1, 3).indexOf('d', 2));
assertEquals(0, new AsciiString("abcd", 1, 2).indexOf('b', 0));
assertEquals(-1, new AsciiString("abcd", 0, 2).indexOf('c', 0));
assertEquals(-1, new AsciiString("abcd", 1, 3).indexOf('a', 0));
}
@Test
public void testIndexOfCharSequence() {
assertEquals(0, new AsciiString("abcd").indexOf("abcd", 0));
assertEquals(0, new AsciiString("abcd").indexOf("abc", 0));
assertEquals(1, new AsciiString("abcd").indexOf("bcd", 0));
assertEquals(1, new AsciiString("abcd").indexOf("bc", 0));
assertEquals(1, new AsciiString("abcdabcd").indexOf("bcd", 0));
assertEquals(0, new AsciiString("abcd", 1, 2).indexOf("bc", 0));
assertEquals(0, new AsciiString("abcd", 1, 3).indexOf("bcd", 0));
assertEquals(1, new AsciiString("abcdabcd", 4, 4).indexOf("bcd", 0));
// Test with empty string
assertEquals(0, new AsciiString("abcd").indexOf("", 0));
assertEquals(1, new AsciiString("abcd").indexOf("", 1));
assertEquals(3, new AsciiString("abcd", 1, 3).indexOf("", 4));
// Test not found
assertEquals(-1, new AsciiString("abcd").indexOf("abcde", 0));
assertEquals(-1, new AsciiString("abcdbc").indexOf("bce", 0));
assertEquals(-1, new AsciiString("abcd", 1, 3).indexOf("abc", 0));
assertEquals(-1, new AsciiString("abcd", 1, 2).indexOf("bd", 0));
}
@Test
@ -315,6 +344,42 @@ public class AsciiStringCharacterTest {
assertEquals(3, AsciiString.indexOf("aabdabaa", 'd', 1));
}
@Test
public void testLastIndexOfCharSequence() {
assertEquals(0, new AsciiString("abcd").lastIndexOf("abcd", 0));
assertEquals(0, new AsciiString("abcd").lastIndexOf("abc", 0));
assertEquals(1, new AsciiString("abcd").lastIndexOf("bcd", 0));
assertEquals(1, new AsciiString("abcd").lastIndexOf("bc", 0));
assertEquals(5, new AsciiString("abcdabcd").lastIndexOf("bcd", 0));
assertEquals(0, new AsciiString("abcd", 1, 2).lastIndexOf("bc", 0));
assertEquals(0, new AsciiString("abcd", 1, 3).lastIndexOf("bcd", 0));
assertEquals(1, new AsciiString("abcdabcd", 4, 4).lastIndexOf("bcd", 0));
// Test with empty string
assertEquals(0, new AsciiString("abcd").lastIndexOf("", 0));
assertEquals(1, new AsciiString("abcd").lastIndexOf("", 1));
assertEquals(3, new AsciiString("abcd", 1, 3).lastIndexOf("", 4));
// Test not found
assertEquals(-1, new AsciiString("abcd").lastIndexOf("abcde", 0));
assertEquals(-1, new AsciiString("abcdbc").lastIndexOf("bce", 0));
assertEquals(-1, new AsciiString("abcd", 1, 3).lastIndexOf("abc", 0));
assertEquals(-1, new AsciiString("abcd", 1, 2).lastIndexOf("bd", 0));
}
@Test
public void testReplace() {
AsciiString abcd = new AsciiString("abcd");
assertEquals(new AsciiString("adcd"), abcd.replace('b', 'd'));
assertEquals(new AsciiString("dbcd"), abcd.replace('a', 'd'));
assertEquals(new AsciiString("abca"), abcd.replace('d', 'a'));
assertSame(abcd, abcd.replace('x', 'a'));
assertEquals(new AsciiString("cc"), new AsciiString("abcd", 1, 2).replace('b', 'c'));
assertEquals(new AsciiString("bb"), new AsciiString("abcd", 1, 2).replace('c', 'b'));
assertEquals(new AsciiString("bddd"), new AsciiString("abcdc", 1, 4).replace('c', 'd'));
assertEquals(new AsciiString("xbcxd"), new AsciiString("abcada", 0, 5).replace('a', 'x'));
}
@Test
public void testSubStringHashCode() {
//two "123"s