ByteString arrayOffset method

Motivation:
The ByteString class currently assumes the underlying array will be a complete representation of data. This is limiting as it does not allow a subsection of another array to be used. The forces copy operations to take place to compensate for the lack of API support.

Modifications:
- add arrayOffset method to ByteString
- modify all ByteString and AsciiString methods that loop over or index into the underlying array to use this offset
- update all code that uses ByteString.array to ensure it accounts for the offset
- add unit tests to test the implementation respects the offset

Result:
ByteString and AsciiString can represent a sub region of a byte[].
This commit is contained in:
Scott Mitchell 2015-04-15 13:55:42 -07:00
parent 70a2608325
commit f812180c2d
10 changed files with 592 additions and 326 deletions

View File

@ -610,7 +610,7 @@ public final class ByteBufUtil {
+ length + ") <= srcLen(" + thisLen + ')'); + length + ") <= srcLen(" + thisLen + ')');
} }
checkNotNull(dst, "dst").setBytes(dstIdx, src.array(), srcIdx, length); checkNotNull(dst, "dst").setBytes(dstIdx, src.array(), srcIdx + src.arrayOffset(), length);
} }
/** /**
@ -628,7 +628,7 @@ public final class ByteBufUtil {
+ length + ") <= srcLen(" + thisLen + ')'); + length + ") <= srcLen(" + thisLen + ')');
} }
checkNotNull(dst, "dst").writeBytes(src.array(), srcIdx, length); checkNotNull(dst, "dst").writeBytes(src.array(), srcIdx + src.arrayOffset(), length);
} }
static final class ThreadLocalUnsafeDirectByteBuf extends UnpooledUnsafeDirectByteBuf { static final class ThreadLocalUnsafeDirectByteBuf extends UnpooledUnsafeDirectByteBuf {

View File

@ -109,7 +109,10 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
} }
private void encodeHeader(ByteString key, ByteString value, OutputStream stream) throws IOException { private void encodeHeader(ByteString key, ByteString value, OutputStream stream) throws IOException {
encoder.encodeHeader(stream, key.array(), value.array(), sensitivityDetector.isSensitive(key, value)); encoder.encodeHeader(stream,
key.isEntireArrayUsed() ? key.array() : new ByteString(key, true).array(),
value.isEntireArrayUsed() ? value.array() : new ByteString(value, true).array(),
sensitivityDetector.isSensitive(key, value));
} }
/** /**

View File

@ -377,7 +377,7 @@ public final class HttpUtil {
throw streamError(streamId, PROTOCOL_ERROR, throw streamError(streamId, PROTOCOL_ERROR,
"Invalid HTTP/2 header '%s' encountered in translation to HTTP/1.x", translatedName); "Invalid HTTP/2 header '%s' encountered in translation to HTTP/1.x", translatedName);
} else { } else {
output.add(new AsciiString(translatedName.array(), false), new AsciiString(value.array(), false)); output.add(new AsciiString(translatedName, false), new AsciiString(value, false));
} }
} }
return true; return true;

View File

@ -24,7 +24,6 @@ import io.netty.util.internal.PlatformDependent;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -69,11 +68,10 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
int length2 = o2.length(); int length2 = o2.length();
int minLength = Math.min(length1, length2); int minLength = Math.min(length1, length2);
if (a1 != null && a2 != null) { if (a1 != null && a2 != null) {
byte[] thisValue = a1.value; final int a1Len = minLength + a1.arrayOffset();
byte[] thatValue = a2.value; for (int i = a1.arrayOffset(), j = a2.arrayOffset(); i < a1Len; i++, j++) {
for (int i = 0; i < minLength; i++) { byte v1 = a1.value[i];
byte v1 = thisValue[i]; byte v2 = a2.value[j];
byte v2 = thatValue[i];
if (v1 == v2) { if (v1 == v2) {
continue; continue;
} }
@ -85,20 +83,18 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
} }
} }
} else if (a1 != null) { } else if (a1 != null) {
byte[] thisValue = a1.value; for (int i = a1.arrayOffset(), j = 0; j < minLength; i++, j++) {
for (int i = 0; i < minLength; i++) { int c1 = toLowerCase(a1.value[i]);
int c1 = toLowerCase(thisValue[i]); int c2 = toLowerCase(o2.charAt(j));
int c2 = toLowerCase(o2.charAt(i));
result = c1 - c2; result = c1 - c2;
if (result != 0) { if (result != 0) {
return result; return result;
} }
} }
} else if (a2 != null) { } else if (a2 != null) {
byte[] thatValue = a2.value; for (int i = 0, j = a2.arrayOffset(); i < minLength; i++, j++) {
for (int i = 0; i < minLength; i++) {
int c1 = toLowerCase(o1.charAt(i)); int c1 = toLowerCase(o1.charAt(i));
int c2 = toLowerCase(thatValue[i]); int c2 = toLowerCase(a2.value[j]);
result = c1 - c2; result = c1 - c2;
if (result != 0) { if (result != 0) {
return result; return result;
@ -134,31 +130,28 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
int length2 = o2.length(); int length2 = o2.length();
int minLength = Math.min(length1, length2); int minLength = Math.min(length1, length2);
if (a1 != null && a2 != null) { if (a1 != null && a2 != null) {
byte[] thisValue = a1.value; final int a1Len = minLength + a1.arrayOffset();
byte[] thatValue = a2.value; for (int i = a1.arrayOffset(), j = a2.arrayOffset(); i < a1Len; i++, j++) {
for (int i = 0; i < minLength; i++) { byte v1 = a1.value[i];
byte v1 = thisValue[i]; byte v2 = a2.value[j];
byte v2 = thatValue[i];
result = v1 - v2; result = v1 - v2;
if (result != 0) { if (result != 0) {
return result; return result;
} }
} }
} else if (a1 != null) { } else if (a1 != null) {
byte[] thisValue = a1.value; for (int i = a1.arrayOffset(), j = 0; j < minLength; i++, j++) {
for (int i = 0; i < minLength; i++) { int c1 = a1.value[i];
int c1 = thisValue[i]; int c2 = o2.charAt(j);
int c2 = o2.charAt(i);
result = c1 - c2; result = c1 - c2;
if (result != 0) { if (result != 0) {
return result; return result;
} }
} }
} else if (a2 != null) { } else if (a2 != null) {
byte[] thatValue = a2.value; for (int i = 0, j = a2.arrayOffset(); i < minLength; i++, j++) {
for (int i = 0; i < minLength; i++) {
int c1 = o1.charAt(i); int c1 = o1.charAt(i);
int c2 = thatValue[i]; int c2 = a2.value[j];
result = c1 - c2; result = c1 - c2;
if (result != 0) { if (result != 0) {
return result; return result;
@ -179,6 +172,16 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
} }
}; };
/**
* Factory which uses the {@link #AsciiString(byte[], int, int, boolean)} constructor.
*/
private static final ByteStringFactory DEFAULT_FACTORY = new ByteStringFactory() {
@Override
public ByteString newInstance(byte[] value, int start, int length, boolean copy) {
return new AsciiString(value, start, length, copy);
}
};
/** /**
* Returns the case-insensitive hash code of the specified string. Note that this method uses the same hashing * Returns the case-insensitive hash code of the specified string. Note that this method uses the same hashing
* algorithm with {@link #hashCode()} so that you can put both {@link AsciiString}s and arbitrary * algorithm with {@link #hashCode()} so that you can put both {@link AsciiString}s and arbitrary
@ -186,15 +189,32 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
*/ */
public static int caseInsensitiveHashCode(CharSequence value) { public static int caseInsensitiveHashCode(CharSequence value) {
if (value instanceof AsciiString) { if (value instanceof AsciiString) {
return value.hashCode(); try {
ByteProcessor processor = new ByteProcessor() {
private int hash;
@Override
public boolean process(byte value) throws Exception {
hash = hash * HASH_CODE_PRIME ^ toLowerCase(value) & HASH_CODE_PRIME;
return true;
}
@Override
public int hashCode() {
return hash;
}
};
((AsciiString) value).forEachByte(processor);
return processor.hashCode();
} catch (Exception e) {
PlatformDependent.throwException(e);
}
} }
int hash = 0; int hash = 0;
final int end = value.length(); final int end = value.length();
for (int i = 0; i < end; i++) { for (int i = 0; i < end; i++) {
hash = hash * HASH_CODE_PRIME ^ value.charAt(i) & HASH_CODE_PRIME; hash = hash * HASH_CODE_PRIME ^ toLowerCase(value.charAt(i)) & HASH_CODE_PRIME;
} }
return hash; return hash;
} }
@ -267,20 +287,16 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
super(value, copy); super(value, copy);
} }
public AsciiString(byte[] value, int start, int length) {
super(value, start, length);
}
public AsciiString(byte[] value, int start, int length, boolean copy) { public AsciiString(byte[] value, int start, int length, boolean copy) {
super(value, start, length, copy); super(value, start, length, copy);
} }
public AsciiString(ByteBuffer value) { public AsciiString(ByteString value, boolean copy) {
super(value); super(value, copy);
} }
public AsciiString(ByteBuffer value, int start, int length) { public AsciiString(ByteBuffer value) {
super(value, start, length); super(value);
} }
public AsciiString(ByteBuffer value, int start, int length, boolean copy) { public AsciiString(ByteBuffer value, int start, int length, boolean copy) {
@ -314,8 +330,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
+ ") <= " + "value.length(" + value.length() + ')'); + ") <= " + "value.length(" + value.length() + ')');
} }
for (int i = 0; i < length; i++) { for (int i = 0, j = start; i < length; i++, j++) {
this.value[i] = c2b(value.charAt(start + i)); this.value[i] = c2b(value.charAt(j));
} }
} }
@ -324,6 +340,12 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return b2c(byteAt(index)); return b2c(byteAt(index));
} }
@Override
public void arrayChanged() {
string = null;
super.arrayChanged();
}
private static byte c2b(char c) { private static byte c2b(char c) {
if (c > MAX_CHAR_VALUE) { if (c > MAX_CHAR_VALUE) {
return '?'; return '?';
@ -381,6 +403,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
* positive integer if this string is after the specified string. * positive integer if this string is after the specified string.
* @throws NullPointerException if {@code string} is {@code null}. * @throws NullPointerException if {@code string} is {@code null}.
*/ */
@Override
public int compareTo(CharSequence string) { public int compareTo(CharSequence string) {
if (this == string) { if (this == string) {
return 0; return 0;
@ -390,9 +413,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
int length1 = length(); int length1 = length();
int length2 = string.length(); int length2 = string.length();
int minLength = Math.min(length1, length2); int minLength = Math.min(length1, length2);
byte[] value = this.value; for (int i = 0, j = arrayOffset(); i < minLength; i++, j++) {
for (int i = 0, j = 0; j < minLength; i++, j++) { result = b2c(value[j]) - string.charAt(i);
result = b2c(value[i]) - string.charAt(j);
if (result != 0) { if (result != 0) {
return result; return result;
} }
@ -438,9 +460,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return that; return that;
} }
byte[] newValue = Arrays.copyOf(value, thisLen + thatLen); byte[] newValue = new byte[thisLen + thatLen];
System.arraycopy(that.value, 0, newValue, thisLen, thatLen); System.arraycopy(value, arrayOffset(), newValue, 0, thisLen);
System.arraycopy(that.value, that.arrayOffset(), newValue, thisLen, thatLen);
return new AsciiString(newValue, false); return new AsciiString(newValue, false);
} }
@ -448,9 +470,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return new AsciiString(string); return new AsciiString(string);
} }
int newLen = thisLen + thatLen; byte[] newValue = new byte[thisLen + thatLen];
byte[] newValue = Arrays.copyOf(value, newLen); System.arraycopy(value, arrayOffset(), newValue, 0, thisLen);
for (int i = thisLen, j = 0; i < newLen; i++, j++) { for (int i = thisLen, j = 0; i < newValue.length; i++, j++) {
newValue[i] = c2b(string.charAt(j)); newValue[i] = c2b(string.charAt(j));
} }
@ -491,8 +513,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return false; return false;
} }
for (int i = 0; i < thisLen; i++) { for (int i = 0, j = arrayOffset(); i < thisLen; i++, j++) {
char c1 = b2c(value[i]); char c1 = b2c(value[j]);
char c2 = string.charAt(i); char c2 = string.charAt(i);
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) { if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
return false; return false;
@ -521,8 +543,13 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return EmptyArrays.EMPTY_CHARS; return EmptyArrays.EMPTY_CHARS;
} }
if (start < 0 || length > length() - start) {
throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= srcIdx + length("
+ length + ") <= srcLen(" + length() + ')');
}
final char[] buffer = new char[length]; final char[] buffer = new char[length];
for (int i = 0, j = start; i < length; i++, j++) { for (int i = 0, j = start + arrayOffset(); i < length; i++, j++) {
buffer[i] = b2c(value[j]); buffer[i] = b2c(value[j]);
} }
return buffer; return buffer;
@ -541,22 +568,30 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
throw new NullPointerException("dst"); throw new NullPointerException("dst");
} }
final int thisLen = value.length; if (srcIdx < 0 || length > length() - srcIdx) {
if (srcIdx < 0 || length > thisLen - srcIdx) {
throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
+ length + ") <= srcLen(" + thisLen + ')'); + length + ") <= srcLen(" + length() + ')');
} }
final int dstEnd = dstIdx + length; final int dstEnd = dstIdx + length;
for (int i = srcIdx, j = dstIdx; j < dstEnd; i++, j++) { for (int i = dstIdx, j = srcIdx + arrayOffset(); i < dstEnd; i++, j++) {
dst[j] = b2c(value[i]); dst[i] = b2c(value[j]);
} }
} }
@Override
public AsciiString subSequence(int start) {
return subSequence(start, length());
}
@Override @Override
public AsciiString subSequence(int start, int end) { public AsciiString subSequence(int start, int end) {
return (AsciiString) super.subSequence(start, end); return subSequence(start, end, true);
}
@Override
public AsciiString subSequence(int start, int end, boolean copy) {
return (AsciiString) super.subSequence(start, end, copy, DEFAULT_FACTORY);
} }
/** /**
@ -587,7 +622,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
start = 0; start = 0;
} }
final int thisLen = value.length; final int thisLen = length();
int subCount = subString.length(); int subCount = subString.length();
if (subCount <= 0) { if (subCount <= 0) {
@ -598,6 +633,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
} }
final char firstChar = subString.charAt(0); final char firstChar = subString.charAt(0);
if (firstChar > MAX_CHAR_VALUE) {
return -1;
}
ByteProcessor IndexOfVisitor = new IndexOfProcessor((byte) firstChar); ByteProcessor IndexOfVisitor = new IndexOfProcessor((byte) firstChar);
try { try {
for (;;) { for (;;) {
@ -606,7 +644,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return -1; // handles subCount > count || start >= count return -1; // handles subCount > count || start >= count
} }
int o1 = i, o2 = 0; int o1 = i, o2 = 0;
while (++o2 < subCount && b2c(value[++o1]) == subString.charAt(o2)) { while (++o2 < subCount && b2c(value[++o1 + arrayOffset()]) == subString.charAt(o2)) {
// Intentionally empty // Intentionally empty
} }
if (o2 == subCount) { if (o2 == subCount) {
@ -645,7 +683,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
* @throws NullPointerException if {@code subString} is {@code null}. * @throws NullPointerException if {@code subString} is {@code null}.
*/ */
public int lastIndexOf(CharSequence subString, int start) { public int lastIndexOf(CharSequence subString, int start) {
final int thisLen = value.length; final int thisLen = length();
final int subCount = subString.length(); final int subCount = subString.length();
if (subCount > thisLen || start < 0) { if (subCount > thisLen || start < 0) {
@ -660,6 +698,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
// count and subCount are both >= 1 // count and subCount are both >= 1
final char firstChar = subString.charAt(0); final char firstChar = subString.charAt(0);
if (firstChar > MAX_CHAR_VALUE) {
return -1;
}
ByteProcessor IndexOfVisitor = new IndexOfProcessor((byte) firstChar); ByteProcessor IndexOfVisitor = new IndexOfProcessor((byte) firstChar);
try { try {
for (;;) { for (;;) {
@ -668,7 +709,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return -1; return -1;
} }
int o1 = i, o2 = 0; int o1 = i, o2 = 0;
while (++o2 < subCount && b2c(value[++o1]) == subString.charAt(o2)) { while (++o2 < subCount && b2c(value[++o1 + arrayOffset()]) == subString.charAt(o2)) {
// Intentionally empty // Intentionally empty
} }
if (o2 == subCount) { if (o2 == subCount) {
@ -702,7 +743,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return false; return false;
} }
final int thisLen = value.length; final int thisLen = length();
if (thisStart < 0 || thisLen - thisStart < length) { if (thisStart < 0 || thisLen - thisStart < length) {
return false; return false;
} }
@ -711,9 +752,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return true; return true;
} }
final int thisEnd = thisStart + length; final int thatEnd = start + length;
for (int i = thisStart, j = start; i < thisEnd; i++, j++) { for (int i = start, j = thisStart + arrayOffset(); i < thatEnd; i++, j++) {
if (b2c(value[i]) != string.charAt(j)) { if (b2c(value[j]) != string.charAt(i)) {
return false; return false;
} }
} }
@ -741,7 +782,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
throw new NullPointerException("string"); throw new NullPointerException("string");
} }
final int thisLen = value.length; final int thisLen = length();
if (thisStart < 0 || length > thisLen - thisStart) { if (thisStart < 0 || length > thisLen - thisStart) {
return false; return false;
} }
@ -749,7 +790,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return false; return false;
} }
int thisEnd = thisStart + length; thisStart += arrayOffset();
final int thisEnd = thisStart + length;
while (thisStart < thisEnd) { while (thisStart < thisEnd) {
char c1 = b2c(value[thisStart++]); char c1 = b2c(value[thisStart++]);
char c2 = string.charAt(start++); char c2 = string.charAt(start++);
@ -784,15 +826,14 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return this; return this;
} }
final int count = value.length;
final byte newCharByte = c2b(newChar); final byte newCharByte = c2b(newChar);
byte[] buffer = new byte[count]; byte[] buffer = new byte[length()];
for (int i = 0, j = 0; i < count; i++, j++) { for (int i = 0, j = arrayOffset(); i < buffer.length; i++, j++) {
byte b = value[i]; byte b = value[j];
if (b == oldCharByte) { if (b == oldCharByte) {
b = newCharByte; b = newCharByte;
} }
buffer[j] = b; buffer[i] = b;
} }
return new AsciiString(buffer, false); return new AsciiString(buffer, false);
@ -831,7 +872,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
public AsciiString toLowerCase() { public AsciiString toLowerCase() {
boolean lowercased = true; boolean lowercased = true;
int i, j; int i, j;
for (i = 0; i < value.length; ++i) { final int len = length() + arrayOffset();
for (i = arrayOffset(); i < len; ++i) {
byte b = value[i]; byte b = value[i];
if (b >= 'A' && b <= 'Z') { if (b >= 'A' && b <= 'Z') {
lowercased = false; lowercased = false;
@ -844,9 +886,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return this; return this;
} }
final int length = value.length; final byte[] newValue = new byte[length()];
final byte[] newValue = new byte[length]; for (i = 0, j = arrayOffset(); i < newValue.length; ++i, ++j) {
for (i = 0, j = 0; i < length; ++i, ++j) {
newValue[i] = toLowerCase(value[j]); newValue[i] = toLowerCase(value[j]);
} }
@ -861,7 +902,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
public AsciiString toUpperCase() { public AsciiString toUpperCase() {
boolean uppercased = true; boolean uppercased = true;
int i, j; int i, j;
for (i = 0; i < value.length; ++i) { final int len = length() + arrayOffset();
for (i = arrayOffset(); i < len; ++i) {
byte b = value[i]; byte b = value[i];
if (b >= 'a' && b <= 'z') { if (b >= 'a' && b <= 'z') {
uppercased = false; uppercased = false;
@ -874,9 +916,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
return this; return this;
} }
final int length = value.length; final byte[] newValue = new byte[length()];
final byte[] newValue = new byte[length]; for (i = 0, j = arrayOffset(); i < newValue.length; ++i, ++j) {
for (i = 0, j = 0; i < length; ++i, ++j) {
newValue[i] = toUpperCase(value[j]); newValue[i] = toUpperCase(value[j]);
} }
@ -889,7 +930,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
* @return a new string with characters {@code <= \\u0020} removed from the beginning and the end. * @return a new string with characters {@code <= \\u0020} removed from the beginning and the end.
*/ */
public AsciiString trim() { public AsciiString trim() {
int start = 0, last = value.length; int start = arrayOffset(), last = arrayOffset() + length();
int end = last; int end = last;
while (start <= end && value[start] <= ' ') { while (start <= end && value[start] <= ' ') {
start++; start++;
@ -969,13 +1010,13 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
final List<AsciiString> res = new ArrayList<AsciiString>(); final List<AsciiString> res = new ArrayList<AsciiString>();
int start = 0; int start = 0;
final int length = value.length; final int length = length();
for (int i = start; i < length; i++) { for (int i = start; i < length; i++) {
if (charAt(i) == delim) { if (charAt(i) == delim) {
if (start == i) { if (start == i) {
res.add(EMPTY_STRING); res.add(EMPTY_STRING);
} else { } else {
res.add(new AsciiString(value, start, i - start, false)); res.add(new AsciiString(value, start + arrayOffset(), i - start, false));
} }
start = i + 1; start = i + 1;
} }
@ -986,7 +1027,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
} else { } else {
if (start != length) { if (start != length) {
// Add the last element if it's not empty. // Add the last element if it's not empty.
res.add(new AsciiString(value, start, length - start, false)); res.add(new AsciiString(value, start + arrayOffset(), length - start, false));
} else { } else {
// Truncate trailing empty elements. // Truncate trailing empty elements.
for (int i = res.size() - 1; i >= 0; i--) { for (int i = res.size() - 1; i >= 0; i--) {
@ -1014,72 +1055,4 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
} }
return indexOf(cs) >= 0; return indexOf(cs) >= 0;
} }
public int parseInt() {
return parseAsciiInt();
}
public int parseInt(int radix) {
return parseAsciiInt(radix);
}
public int parseInt(int start, int end) {
return parseAsciiInt(start, end);
}
public int parseInt(int start, int end, int radix) {
return parseAsciiInt(start, end, radix);
}
public long parseLong() {
return parseAsciiLong();
}
public long parseLong(int radix) {
return parseAsciiLong(radix);
}
public long parseLong(int start, int end) {
return parseAsciiLong(start, end);
}
public long parseLong(int start, int end, int radix) {
return parseAsciiLong(start, end, radix);
}
public char parseChar(int start) {
return charAt(start);
}
public short parseShort() {
return parseAsciiShort();
}
public short parseShort(int radix) {
return parseAsciiShort(radix);
}
public short parseShort(int start, int end) {
return parseAsciiShort(start, end);
}
public short parseShort(int start, int end, int radix) {
return parseAsciiShort(start, end, radix);
}
public float parseFloat() {
return parseAsciiFloat();
}
public float parseFloat(int start, int end) {
return parseAsciiFloat(start, end);
}
public double parseDouble() {
return parseAsciiDouble();
}
public double parseDouble(int start, int end) {
return parseAsciiDouble(start, end);
}
} }

View File

@ -47,7 +47,7 @@ public class ByteString {
int length1 = o1.length(); int length1 = o1.length();
int length2 = o2.length(); int length2 = o2.length();
int minLength = Math.min(length1, length2); int minLength = Math.min(length1, length2);
for (int i = 0, j = 0; j < minLength; i++, j++) { for (int i = o1.offset, j = o2.offset; i < minLength; i++, j++) {
result = o1.value[i] - o2.value[j]; result = o1.value[i] - o2.value[j];
if (result != 0) { if (result != 0) {
return result; return result;
@ -57,13 +57,41 @@ public class ByteString {
return length1 - length2; return length1 - length2;
} }
}; };
/**
* Allows sub classes to take advantage of {@link ByteString} operations which need to generate new
* ByteString objects.
*/
protected interface ByteStringFactory {
ByteString newInstance(byte[] value, int start, int length, boolean copy);
}
/**
* Factory which uses the {@link #ByteString(byte[], int, int, boolean)} constructor.
*/
private static final ByteStringFactory DEFAULT_FACTORY = new ByteStringFactory() {
@Override
public ByteString newInstance(byte[] value, int start, int length, boolean copy) {
return new ByteString(value, start, length, copy);
}
};
public static final ByteString EMPTY_STRING = new ByteString(0); public static final ByteString EMPTY_STRING = new ByteString(0);
protected static final int HASH_CODE_PRIME = 31; protected static final int HASH_CODE_PRIME = 31;;
/** /**
* If this value is modified outside the constructor then call {@link #arrayChanged()}. * If this value is modified outside the constructor then call {@link #arrayChanged()}.
*/ */
protected final byte[] value; protected final byte[] value;
/**
* Offset into {@link #value} that all operations should use when acting upon {@link #value}.
*/
private final int offset;
/**
* Length in bytes for {@link #value} that we care about. This is independent from {@code value.length}
* because we may be looking at a subsection of the array.
*/
private final int length;
/** /**
* The hash code is cached after it is first computed. It can be reset with {@link #arrayChanged()}. * The hash code is cached after it is first computed. It can be reset with {@link #arrayChanged()}.
*/ */
@ -72,7 +100,11 @@ public class ByteString {
/** /**
* Used for classes which extend this class and want to initialize the {@link #value} array by them selves. * Used for classes which extend this class and want to initialize the {@link #value} array by them selves.
*/ */
ByteString(int length) { value = new byte[length]; } ByteString(int length) {
value = new byte[length];
offset = 0;
this.length = length;
}
/** /**
* Initialize this byte string based upon a byte array. A copy will be made. * Initialize this byte string based upon a byte array. A copy will be made.
@ -86,24 +118,13 @@ public class ByteString {
* {@code copy} determines if a copy is made or the array is shared. * {@code copy} determines if a copy is made or the array is shared.
*/ */
public ByteString(byte[] value, boolean copy) { public ByteString(byte[] value, boolean copy) {
if (copy) { this(value, 0, checkNotNull(value, "value").length, copy);
this.value = checkNotNull(value, "value").clone();
} else {
this.value = checkNotNull(value, "value");
}
}
/**
* Initialize this byte string based upon a range of a byte array. A copy will be made.
*/
public ByteString(byte[] value, int start, int length) {
this(value, start, length, true);
} }
/** /**
* Construct a new {@link BinaryString} object from a {@code byte[]} array. * Construct a new {@link BinaryString} object from a {@code byte[]} array.
* @param copy {@code true} then a copy of the memory will be made. {@code false} the underlying memory * @param copy {@code true} then a copy of the memory will be made. {@code false} the underlying memory
* will be shared. If this shared memory changes then {@link #arrayChanged()} must be called. * will be shared.
*/ */
public ByteString(byte[] value, int start, int length, boolean copy) { public ByteString(byte[] value, int start, int length, boolean copy) {
if (start < 0 || start > checkNotNull(value, "value").length - length) { if (start < 0 || start > checkNotNull(value, "value").length - length) {
@ -111,10 +132,34 @@ public class ByteString {
+ ") <= " + "value.length(" + value.length + ')'); + ") <= " + "value.length(" + value.length + ')');
} }
if (copy || start != 0 || length != value.length) { if (copy) {
this.value = Arrays.copyOfRange(value, start, start + length); this.value = Arrays.copyOfRange(value, start, start + length);
offset = 0;
this.length = length;
} else { } else {
this.value = value; this.value = value;
this.offset = start;
this.length = length;
}
}
/**
* Create a new object which is equal to {@code value}.
* @param value The object to replicate.
* @param copy {@code true} mean the underlying storage will be copied.
* {@code false} means the underlying storage will be shared.
*/
public ByteString(ByteString value, boolean copy) {
checkNotNull(value, "value");
this.length = value.length();
this.hash = value.hash;
if (copy) {
this.value = new byte[length];
System.arraycopy(value.array(), value.arrayOffset(), this.value, 0, length);
this.offset = 0;
} else {
this.value = value.array();
this.offset = value.offset;
} }
} }
@ -123,32 +168,57 @@ public class ByteString {
* The copy will start at {@link ByteBuffer#position()} and copy {@link ByteBuffer#remaining()} bytes. * The copy will start at {@link ByteBuffer#position()} and copy {@link ByteBuffer#remaining()} bytes.
*/ */
public ByteString(ByteBuffer value) { public ByteString(ByteBuffer value) {
this.value = getBytes(value); this(value, true);
}
/**
* Create a copy of the underlying storage from {@link value}.
* The copy will start at {@code start} and copy {@code length} bytes.
*/
public ByteString(ByteBuffer value, int start, int length) {
this.value = getBytes(value, start, length, true);
} }
/** /**
* Initialize a {@link ByteString} based upon the underlying storage from {@link value}. * Initialize a {@link ByteString} based upon the underlying storage from {@link value}.
* The copy will start at {@code start} and copy {@code length} bytes. * There is a potential to share the underlying array storage if {@link ByteBuffer#hasArray()} is {@code true}.
* if {@code copy} is true a copy will be made of the memory. * if {@code copy} is {@code true} a copy will be made of the memory.
* if {@code copy} is false the underlying storage will be shared, if possible. * if {@code copy} is {@code false} the underlying storage will be shared, if possible.
*/
public ByteString(ByteBuffer value, boolean copy) {
this(value, value.position(), checkNotNull(value, "value").remaining(), copy);
}
/**
* Initialize a {@link ByteString} based upon the underlying storage from {@link value}.
* There is a potential to share the underlying array storage if {@link ByteBuffer#hasArray()} is {@code true}.
* if {@code copy} is {@code true} a copy will be made of the memory.
* if {@code copy} is {@code false} the underlying storage will be shared, if possible.
*/ */
public ByteString(ByteBuffer value, int start, int length, boolean copy) { public ByteString(ByteBuffer value, int start, int length, boolean copy) {
this.value = getBytes(value, start, length, copy); if (start < 0 || length > checkNotNull(value, "value").capacity() - start) {
throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
+ ") <= " + "value.capacity(" + value.capacity() + ')');
}
if (value.hasArray()) {
if (copy) {
final int bufferOffset = value.arrayOffset() + start;
this.value = Arrays.copyOfRange(value.array(), bufferOffset, bufferOffset + length);
offset = 0;
this.length = length;
} else {
this.value = value.array();
this.offset = start;
this.length = length;
}
} else {
this.value = new byte[length];
int oldPos = value.position();
value.get(this.value, 0, length);
value.position(oldPos);
this.offset = 0;
this.length = length;
}
} }
/** /**
* Create a copy of {@link value} into a {@link ByteString} using the encoding type of {@code charset}. * Create a copy of {@link value} into a {@link ByteString} using the encoding type of {@code charset}.
*/ */
public ByteString(char[] value, Charset charset) { public ByteString(char[] value, Charset charset) {
this.value = getBytes(value, charset); this(value, charset, 0, checkNotNull(value, "value").length);
} }
/** /**
@ -156,80 +226,6 @@ public class ByteString {
* The copy will start at index {@code start} and copy {@code length} bytes. * The copy will start at index {@code start} and copy {@code length} bytes.
*/ */
public ByteString(char[] value, Charset charset, int start, int length) { public ByteString(char[] value, Charset charset, int start, int length) {
this.value = getBytes(value, charset, start, length);
}
/**
* Create a copy of {@link value} into a {@link ByteString} using the encoding type of {@code charset}.
*/
public ByteString(CharSequence value, Charset charset) {
this.value = getBytes(value, charset);
}
/**
* Create a copy of {@link value} into a {@link ByteString} using the encoding type of {@code charset}.
* The copy will start at index {@code start} and copy {@code length} bytes.
*/
public ByteString(CharSequence value, Charset charset, int start, int length) {
this.value = getBytes(value, charset, start, length);
}
/**
* Create a copy of the underlying storage from {@link value} into a byte array.
* The copy will start at {@link ByteBuffer#position()} and copy {@link ByteBuffer#remaining()} bytes.
*/
private static byte[] getBytes(ByteBuffer value) {
return getBytes(value, value.position(), checkNotNull(value, "value").remaining());
}
/**
* Create a copy of the underlying storage from {@link value} into a byte array.
* The copy will start at {@code start} and copy {@code length} bytes.
*/
private static byte[] getBytes(ByteBuffer value, int start, int length) {
return getBytes(value, start, length, true);
}
/**
* Return an array of the underlying storage from {@link value} into a byte array.
* The copy will start at {@code start} and copy {@code length} bytes.
* if {@code copy} is true a copy will be made of the memory.
* if {@code copy} is false the underlying storage will be shared, if possible.
*/
private static byte[] getBytes(ByteBuffer value, int start, int length, boolean copy) {
if (start < 0 || length > checkNotNull(value, "value").capacity() - start) {
throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
+ ") <= " + "value.capacity(" + value.capacity() + ')');
}
if (value.hasArray()) {
if (copy || start != 0 || length != value.capacity()) {
int baseOffset = value.arrayOffset() + start;
return Arrays.copyOfRange(value.array(), baseOffset, baseOffset + length);
} else {
return value.array();
}
}
byte[] v = new byte[length];
int oldPos = value.position();
value.get(v, 0, length);
value.position(oldPos);
return v;
}
/**
* Create a copy of {@link value} into a byte array using the encoding type of {@code charset}.
*/
private static byte[] getBytes(char[] value, Charset charset) {
return getBytes(value, charset, 0, checkNotNull(value, "value").length);
}
/**
* Create a copy of {@link value} into a byte array using the encoding type of {@code charset}.
* The copy will start at index {@code start} and copy {@code length} bytes.
*/
private static byte[] getBytes(char[] value, Charset charset, int start, int length) {
if (start < 0 || length > checkNotNull(value, "value").length - start) { if (start < 0 || length > checkNotNull(value, "value").length - start) {
throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
+ ") <= " + "length(" + length + ')'); + ") <= " + "length(" + length + ')');
@ -239,22 +235,24 @@ public class ByteString {
CharsetEncoder encoder = CharsetUtil.getEncoder(charset); CharsetEncoder encoder = CharsetUtil.getEncoder(charset);
ByteBuffer nativeBuffer = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * length)); ByteBuffer nativeBuffer = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * length));
encoder.encode(cbuf, nativeBuffer, true); encoder.encode(cbuf, nativeBuffer, true);
final int offset = nativeBuffer.arrayOffset(); final int bufferOffset = nativeBuffer.arrayOffset();
return Arrays.copyOfRange(nativeBuffer.array(), offset, offset + nativeBuffer.position()); this.value = Arrays.copyOfRange(nativeBuffer.array(), bufferOffset, bufferOffset + nativeBuffer.position());
this.offset = 0;
this.length = this.value.length;
} }
/** /**
* Create a copy of {@link value} into a byte array using the encoding type of {@code charset}. * Create a copy of {@link value} into a {@link ByteString} using the encoding type of {@code charset}.
*/ */
private static byte[] getBytes(CharSequence value, Charset charset) { public ByteString(CharSequence value, Charset charset) {
return getBytes(value, charset, 0, checkNotNull(value, "value").length()); this(value, charset, 0, checkNotNull(value, "value").length());
} }
/** /**
* Create a copy of {@link value} into a byte array using the encoding type of {@code charset}. * Create a copy of {@link value} into a {@link ByteString} using the encoding type of {@code charset}.
* The copy will start at index {@code start} and copy {@code length} bytes. * The copy will start at index {@code start} and copy {@code length} bytes.
*/ */
private static byte[] getBytes(CharSequence value, Charset charset, int start, int length) { public ByteString(CharSequence value, Charset charset, int start, int length) {
if (start < 0 || length > checkNotNull(value, "value").length() - start) { if (start < 0 || length > checkNotNull(value, "value").length() - start) {
throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
+ ") <= " + "length(" + value.length() + ')'); + ") <= " + "length(" + value.length() + ')');
@ -265,7 +263,9 @@ public class ByteString {
ByteBuffer nativeBuffer = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * length)); ByteBuffer nativeBuffer = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * length));
encoder.encode(cbuf, nativeBuffer, true); encoder.encode(cbuf, nativeBuffer, true);
final int offset = nativeBuffer.arrayOffset(); final int offset = nativeBuffer.arrayOffset();
return Arrays.copyOfRange(nativeBuffer.array(), offset, offset + nativeBuffer.position()); this.value = Arrays.copyOfRange(nativeBuffer.array(), offset, offset + nativeBuffer.position());
this.offset = 0;
this.length = this.value.length;
} }
/** /**
@ -275,7 +275,7 @@ public class ByteString {
* The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}. * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
*/ */
public final int forEachByte(ByteProcessor visitor) throws Exception { public final int forEachByte(ByteProcessor visitor) throws Exception {
return forEachByte(0, value.length, visitor); return forEachByte(0, length(), visitor);
} }
/** /**
@ -286,14 +286,15 @@ public class ByteString {
* The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}. * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
*/ */
public final int forEachByte(int index, int length, ByteProcessor visitor) throws Exception { public final int forEachByte(int index, int length, ByteProcessor visitor) throws Exception {
if (index < 0 || length > value.length - index) { if (index < 0 || length > length() - index) {
throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length
+ ") <= " + "length(" + value.length + ')'); + ") <= " + "length(" + length() + ')');
} }
for (int i = index; i < length; ++i) { final int len = offset + length;
for (int i = offset + index; i < len; ++i) {
if (!visitor.process(value[i])) { if (!visitor.process(value[i])) {
return i; return i - offset;
} }
} }
return -1; return -1;
@ -317,36 +318,42 @@ public class ByteString {
* The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}. * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}.
*/ */
public final int forEachByteDesc(int index, int length, ByteProcessor visitor) throws Exception { public final int forEachByteDesc(int index, int length, ByteProcessor visitor) throws Exception {
if (index < 0 || length > value.length - index) { if (index < 0 || length > length() - index) {
throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length throw new IndexOutOfBoundsException("expected: " + "0 <= index(" + index + ") <= start + length(" + length
+ ") <= " + "length(" + value.length + ')'); + ") <= " + "length(" + length() + ')');
} }
for (int i = index + length - 1; i >= index; --i) { final int end = offset + index;
for (int i = offset + index + length - 1; i >= end; --i) {
if (!visitor.process(value[i])) { if (!visitor.process(value[i])) {
return i; return i - offset;
} }
} }
return -1; return -1;
} }
public final byte byteAt(int index) { public final byte byteAt(int index) {
return value[index]; // We must do a range check here to enforce the access does not go outside our sub region of the array.
// We rely on the array access itself to pick up the array out of bounds conditions
if (index < 0 || index >= length) {
throw new IndexOutOfBoundsException("index: " + index + " must be in the range [0," + length + ")");
}
return value[index + offset];
} }
public final boolean isEmpty() { public final boolean isEmpty() {
return value.length == 0; return length == 0;
} }
public final int length() { public final int length() {
return value.length; return length;
} }
/** /**
* During normal use cases the {@link ByteString} should be immutable, but if the underlying array is shared, * During normal use cases the {@link ByteString} should be immutable, but if the underlying array is shared,
* and changes then this needs to be called. * and changes then this needs to be called.
*/ */
public final void arrayChanged() { public void arrayChanged() {
hash = 0; hash = 0;
} }
@ -354,11 +361,30 @@ public class ByteString {
* This gives direct access to the underlying storage array. * This gives direct access to the underlying storage array.
* The {@link #toByteArray()} should be preferred over this method. * The {@link #toByteArray()} should be preferred over this method.
* If the return value is changed then {@link #arrayChanged()} must be called. * If the return value is changed then {@link #arrayChanged()} must be called.
* @see #arrayOffset()
* @see #isEntireArrayUsed()
*/ */
public final byte[] array() { public final byte[] array() {
return value; return value;
} }
/**
* The offset into {@link #array()} for which data for this ByteString begins.
* @see #array()
* @see #isEntireArrayUsed()
*/
public final int arrayOffset() {
return offset;
}
/**
* Determine if the storage represented by {@link #array()} is entirely used.
* @see #array()
*/
public final boolean isEntireArrayUsed() {
return offset == 0 && length == value.length;
}
/** /**
* Converts this string to a byte array. * Converts this string to a byte array.
*/ */
@ -371,7 +397,7 @@ public class ByteString {
* The subset is defined by the range [{@code start}, {@code end}). * The subset is defined by the range [{@code start}, {@code end}).
*/ */
public final byte[] toByteArray(int start, int end) { public final byte[] toByteArray(int start, int end) {
return Arrays.copyOfRange(value, start, end); return Arrays.copyOfRange(value, start + offset, end + offset);
} }
/** /**
@ -383,47 +409,79 @@ public class ByteString {
* @param length the number of characters to copy. * @param length the number of characters to copy.
*/ */
public final void copy(int srcIdx, byte[] dst, int dstIdx, int length) { public final void copy(int srcIdx, byte[] dst, int dstIdx, int length) {
final int thisLen = value.length; if (srcIdx < 0 || length > length() - srcIdx) {
if (srcIdx < 0 || length > thisLen - srcIdx) {
throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
+ length + ") <= srcLen(" + thisLen + ')'); + length + ") <= srcLen(" + length() + ')');
} }
System.arraycopy(value, srcIdx, checkNotNull(dst, "dst"), dstIdx, length); System.arraycopy(value, srcIdx + offset, checkNotNull(dst, "dst"), dstIdx, length);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int h = hash; int h = hash;
if (h == 0) { if (h == 0) {
for (int i = 0; i < value.length; ++i) { final int end = offset + length;
for (int i = offset; i < end; ++i) {
h = h * HASH_CODE_PRIME ^ value[i] & HASH_CODE_PRIME; h = h * HASH_CODE_PRIME ^ value[i] & HASH_CODE_PRIME;
} }
hash = h; hash = h;
} }
return h; return hash;
} }
/** /**
* Copies a range of characters into a new string. * Copies a range of characters into a new string.
* * @param start the offset of the first character (inclusive).
* @param start the offset of the first character.
* @return a new string containing the characters from start to the end of the string. * @return a new string containing the characters from start to the end of the string.
* @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}. * @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}.
*/ */
public final ByteString subSequence(int start) { public ByteString subSequence(int start) {
return subSequence(start, length()); return subSequence(start, length());
} }
/**
* Copies a range of characters into a new string.
* @param start the offset of the first character (inclusive).
* @param end The index to stop at (exclusive).
* @return a new string containing the characters from start to the end of the string.
* @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}.
*/
public ByteString subSequence(int start, int end) { public ByteString subSequence(int start, int end) {
return subSequence(start, end, true);
}
/**
* Either copy or share a subset of underlying sub-sequence of bytes.
* @param start the offset of the first character (inclusive).
* @param end The index to stop at (exclusive).
* @param copy If {@code true} then a copy of the underlying storage will be made.
* If {@code false} then the underlying storage will be shared.
* @return a new string containing the characters from start to the end of the string.
* @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}.
*/
public ByteString subSequence(int start, int end, boolean copy) {
return subSequence(start, end, copy, DEFAULT_FACTORY);
}
/**
* Either copy or share a subset of underlying sub-sequence of bytes.
* @param start the offset of the first character (inclusive).
* @param end The index to stop at (exclusive).
* @param copy If {@code true} then a copy of the underlying storage will be made.
* If {@code false} then the underlying storage will be shared.
* @param factory The factory used to generate a new {@link ByteString} object.
* @return a new string containing the characters from start to the end of the string.
* @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}.
*/
protected ByteString subSequence(int start, int end, boolean copy, ByteStringFactory factory) {
if (start < 0 || start > end || end > length()) { if (start < 0 || start > end || end > length()) {
throw new IndexOutOfBoundsException("expected: 0 <= start(" + start + ") <= end (" + end + ") <= length(" throw new IndexOutOfBoundsException("expected: 0 <= start(" + start + ") <= end (" + end + ") <= length("
+ length() + ')'); + length() + ')');
} }
if (start == 0 && end == value.length) { if (start == 0 && end == length()) {
return this; return this;
} }
@ -431,7 +489,7 @@ public class ByteString {
return EMPTY_STRING; return EMPTY_STRING;
} }
return new ByteString(value, start, end - start, false); return factory.newInstance(value, start + offset, end - start, copy);
} }
public final int parseAsciiInt() { public final int parseAsciiInt() {
@ -458,7 +516,7 @@ public class ByteString {
int i = start; int i = start;
boolean negative = byteAt(i) == '-'; boolean negative = byteAt(i) == '-';
if (negative && ++i == end) { if (negative && ++i == end) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
return parseAsciiInt(i, end, radix, negative); return parseAsciiInt(i, end, radix, negative);
@ -467,25 +525,25 @@ public class ByteString {
private int parseAsciiInt(int start, int end, int radix, boolean negative) { private int parseAsciiInt(int start, int end, int radix, boolean negative) {
int max = Integer.MIN_VALUE / radix; int max = Integer.MIN_VALUE / radix;
int result = 0; int result = 0;
int offset = start; int currOffset = start;
while (offset < end) { while (currOffset < end) {
int digit = Character.digit((char) (value[offset++] & 0xFF), radix); int digit = Character.digit((char) (value[currOffset++ + offset] & 0xFF), radix);
if (digit == -1) { if (digit == -1) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
if (max > result) { if (max > result) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
int next = result * radix - digit; int next = result * radix - digit;
if (next > result) { if (next > result) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
result = next; result = next;
} }
if (!negative) { if (!negative) {
result = -result; result = -result;
if (result < 0) { if (result < 0) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
} }
return result; return result;
@ -515,7 +573,7 @@ public class ByteString {
int i = start; int i = start;
boolean negative = byteAt(i) == '-'; boolean negative = byteAt(i) == '-';
if (negative && ++i == end) { if (negative && ++i == end) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
return parseAsciiLong(i, end, radix, negative); return parseAsciiLong(i, end, radix, negative);
@ -524,25 +582,25 @@ public class ByteString {
private long parseAsciiLong(int start, int end, int radix, boolean negative) { private long parseAsciiLong(int start, int end, int radix, boolean negative) {
long max = Long.MIN_VALUE / radix; long max = Long.MIN_VALUE / radix;
long result = 0; long result = 0;
int offset = start; int currOffset = start;
while (offset < end) { while (currOffset < end) {
int digit = Character.digit((char) (value[offset++] & 0xFF), radix); int digit = Character.digit((char) (value[currOffset++ + offset] & 0xFF), radix);
if (digit == -1) { if (digit == -1) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
if (max > result) { if (max > result) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
long next = result * radix - digit; long next = result * radix - digit;
if (next > result) { if (next > result) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
result = next; result = next;
} }
if (!negative) { if (!negative) {
result = -result; result = -result;
if (result < 0) { if (result < 0) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
} }
return result; return result;
@ -553,11 +611,12 @@ public class ByteString {
} }
public char parseChar(int start) { public char parseChar(int start) {
if (start + 1 >= value.length) { if (start + 1 >= length()) {
throw new IndexOutOfBoundsException("2 bytes required to convert to character. index " + throw new IndexOutOfBoundsException("2 bytes required to convert to character. index " +
start + " would go out of bounds."); start + " would go out of bounds.");
} }
return (char) (((value[start] & 0xFF) << 8) | (value[start + 1] & 0xFF)); final int startWithOffset = start + offset;
return (char) (((value[startWithOffset] & 0xFF) << 8) | (value[startWithOffset + 1] & 0xFF));
} }
public final short parseAsciiShort() { public final short parseAsciiShort() {
@ -576,7 +635,7 @@ public class ByteString {
int intValue = parseAsciiInt(start, end, radix); int intValue = parseAsciiInt(start, end, radix);
short result = (short) intValue; short result = (short) intValue;
if (result != intValue) { if (result != intValue) {
throw new NumberFormatException(subSequence(start, end).toString()); throw new NumberFormatException(subSequence(start, end, false).toString());
} }
return result; return result;
} }
@ -605,9 +664,11 @@ public class ByteString {
if (this == obj) { if (this == obj) {
return true; return true;
} }
ByteString other = (ByteString) obj; ByteString other = (ByteString) obj;
return hashCode() == other.hashCode() && return hashCode() == other.hashCode() &&
PlatformDependent.equals(array(), 0, array().length, other.array(), 0, other.array().length); PlatformDependent.equals(array(), arrayOffset(), arrayOffset() + length(),
other.array(), other.arrayOffset(), other.arrayOffset() + other.length());
} }
/** /**
@ -645,6 +706,11 @@ public class ByteString {
return StringUtil.EMPTY_STRING; return StringUtil.EMPTY_STRING;
} }
return new String(value, start, length, charset); if (start < 0 || length > length() - start) {
throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= srcIdx + length("
+ length + ") <= srcLen(" + length() + ')');
}
return new String(value, start + offset, length, charset);
} }
} }

View File

@ -863,8 +863,9 @@ public final class PlatformDependent {
if (len1 != len2) { if (len1 != len2) {
return false; return false;
} }
for (int i = 0; i < len1; i++) { final int end = startPos1 + len1;
if (bytes1[startPos1 + i] != bytes2[startPos2 + i]) { for (int i = startPos1, j = startPos2; i < end; ++i, ++j) {
if (bytes1[i] != bytes2[j]) {
return false; return false;
} }
} }

View File

@ -23,7 +23,7 @@ void convertTemplates(String templateDir,
def keyName = keyPrimitive.capitalize() def keyName = keyPrimitive.capitalize()
def replaceFrom = "(^.*)K([^.]+)\\.template\$" def replaceFrom = "(^.*)K([^.]+)\\.template\$"
def replaceTo = "\\1" + keyName + "\\2.java" def replaceTo = "\\1" + keyName + "\\2.java"
def hashCodeFn = keyPrimitive.equals("long") ? "(int)(key ^ (key >>> 32))" : "(int) key" def hashCodeFn = keyPrimitive.equals("long") ? "(int) (key ^ (key >>> 32))" : "(int) key"
ant.copy(todir: outputDir) { ant.copy(todir: outputDir) {
fileset(dir: templateDir) { fileset(dir: templateDir) {
include(name: "**/*.template") include(name: "**/*.template")

View File

@ -15,6 +15,7 @@
*/ */
package io.netty.util; package io.netty.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -26,7 +27,6 @@ import org.junit.Test;
* Test for the {@link AsciiString} class * Test for the {@link AsciiString} class
*/ */
public class AsciiStringTest { public class AsciiStringTest {
@Test @Test
public void testGetBytesStringBuilder() { public void testGetBytesStringBuilder() {
final StringBuilder b = new StringBuilder(); final StringBuilder b = new StringBuilder();
@ -78,4 +78,25 @@ public class AsciiStringTest {
AsciiString ascii = new AsciiString(string.toCharArray()); AsciiString ascii = new AsciiString(string.toCharArray());
Assert.assertEquals(string, ascii.toString()); Assert.assertEquals(string, ascii.toString());
} }
@Test
public void subSequenceTest() {
byte[] init = {'t', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 't', 'e', 's', 't' };
AsciiString ascii = new AsciiString(init);
final int start = 2;
final int end = init.length;
AsciiString sub1 = ascii.subSequence(start, end, false);
AsciiString sub2 = ascii.subSequence(start, end, true);
assertEquals(sub1, sub2);
for (int i = start; i < end; ++i) {
assertEquals(init[i], sub1.byteAt(i - start));
}
}
@Test
public void caseInsensativeHasher() {
String s1 = new String("TransfeR-EncodinG");
AsciiString s2 = new AsciiString("transfer-encoding");
assertEquals(AsciiString.caseInsensitiveHashCode(s1), AsciiString.caseInsensitiveHashCode(s2));
}
} }

View File

@ -0,0 +1,204 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.Test;
/**
* Test for the {@link ByteString} class.
*/
public class ByteStringTest {
private byte[] a;
private byte[] b;
private int aOffset = 22;
private int bOffset = 53;
private int length = 100;
private ByteString aByteString;
private ByteString bByteString;
private ByteString greaterThanAByteString;
private ByteString lessThanAByteString;
private Random r = new Random();
@Before
public void setup() {
a = new byte[128];
b = new byte[256];
r.nextBytes(a);
r.nextBytes(b);
aOffset = 22;
bOffset = 53;
length = 100;
System.arraycopy(a, aOffset, b, bOffset, length);
aByteString = new ByteString(a, aOffset, length, false);
bByteString = new ByteString(b, bOffset, length, false);
int i;
// Find an element that can be decremented
for (i = 1; i < length; ++i) {
if (a[aOffset + 1] > Byte.MIN_VALUE) {
--a[aOffset + 1];
break;
}
}
lessThanAByteString = new ByteString(a, aOffset, length, true);
++a[aOffset + i]; // Restore the a array to the original value
// Find an element that can be incremented
for (i = 1; i < length; ++i) {
if (a[aOffset + 1] < Byte.MAX_VALUE) {
++a[aOffset + 1];
break;
}
}
greaterThanAByteString = new ByteString(a, aOffset, length, true);
--a[aOffset + i]; // Restore the a array to the original value
}
@Test
public void testEqualsComparareSelf() {
assertTrue(ByteString.DEFAULT_COMPARATOR.compare(aByteString, aByteString) == 0);
assertEquals(bByteString, bByteString);
}
@Test
public void testEqualsComparatorAgainstAnother1() {
assertTrue(ByteString.DEFAULT_COMPARATOR.compare(bByteString, aByteString) == 0);
assertEquals(bByteString, aByteString);
assertEquals(bByteString.hashCode(), aByteString.hashCode());
}
@Test
public void testEqualsComparatorAgainstAnother2() {
assertTrue(ByteString.DEFAULT_COMPARATOR.compare(aByteString, bByteString) == 0);
assertEquals(aByteString, bByteString);
assertEquals(aByteString.hashCode(), bByteString.hashCode());
}
@Test
public void testLessThan() {
assertTrue(ByteString.DEFAULT_COMPARATOR.compare(lessThanAByteString, aByteString) < 0);
assertTrue(ByteString.DEFAULT_COMPARATOR.compare(aByteString, lessThanAByteString) > 0);
}
@Test
public void testGreaterThan() {
assertTrue(ByteString.DEFAULT_COMPARATOR.compare(greaterThanAByteString, aByteString) > 0);
assertTrue(ByteString.DEFAULT_COMPARATOR.compare(aByteString, greaterThanAByteString) < 0);
}
@Test
public void testSharedMemory() {
++a[aOffset];
ByteString aByteString1 = new ByteString(a, aOffset, length, true);
ByteString aByteString2 = new ByteString(a, aOffset, length, false);
assertEquals(aByteString, aByteString1);
assertEquals(aByteString, aByteString2);
for (int i = aOffset; i < length; ++i) {
assertEquals(a[i], aByteString.byteAt(i - aOffset));
}
}
@Test
public void testNotSharedMemory() {
ByteString aByteString1 = new ByteString(a, aOffset, length, true);
++a[aOffset];
assertNotEquals(aByteString, aByteString1);
int i = aOffset;
assertNotEquals(a[i], aByteString1.byteAt(i - aOffset));
++i;
for (; i < length; ++i) {
assertEquals(a[i], aByteString1.byteAt(i - aOffset));
}
}
@Test
public void forEachTest() throws Exception {
final AtomicReference<Integer> aCount = new AtomicReference<Integer>(0);
final AtomicReference<Integer> bCount = new AtomicReference<Integer>(0);
aByteString.forEachByte(new ByteProcessor() {
int i;
@Override
public boolean process(byte value) throws Exception {
assertEquals("failed at index: " + i, value, bByteString.byteAt(i++));
aCount.set(aCount.get() + 1);
return true;
}
});
bByteString.forEachByte(new ByteProcessor() {
int i;
@Override
public boolean process(byte value) throws Exception {
assertEquals("failed at index: " + i, value, aByteString.byteAt(i++));
bCount.set(bCount.get() + 1);
return true;
}
});
assertEquals(aByteString.length(), aCount.get().intValue());
assertEquals(bByteString.length(), bCount.get().intValue());
}
@Test
public void forEachDescTest() throws Exception {
final AtomicReference<Integer> aCount = new AtomicReference<Integer>(0);
final AtomicReference<Integer> bCount = new AtomicReference<Integer>(0);
aByteString.forEachByteDesc(new ByteProcessor() {
int i = 1;
@Override
public boolean process(byte value) throws Exception {
assertEquals("failed at index: " + i, value, bByteString.byteAt(bByteString.length() - (i++)));
aCount.set(aCount.get() + 1);
return true;
}
});
bByteString.forEachByteDesc(new ByteProcessor() {
int i = 1;
@Override
public boolean process(byte value) throws Exception {
assertEquals("failed at index: " + i, value, aByteString.byteAt(aByteString.length() - (i++)));
bCount.set(bCount.get() + 1);
return true;
}
});
assertEquals(aByteString.length(), aCount.get().intValue());
assertEquals(bByteString.length(), bCount.get().intValue());
}
@Test
public void subSequenceTest() {
final int start = 12;
final int end = aByteString.length();
ByteString aSubSequence = aByteString.subSequence(start, end, false);
ByteString bSubSequence = bByteString.subSequence(start, end, true);
assertEquals(aSubSequence, bSubSequence);
assertEquals(aSubSequence.hashCode(), bSubSequence.hashCode());
}
@Test
public void copyTest() {
byte[] aCopy = new byte[aByteString.length()];
aByteString.copy(0, aCopy, 0, aCopy.length);
ByteString aByteStringCopy = new ByteString(aCopy, false);
assertEquals(aByteString, aByteStringCopy);
}
}

View File

@ -18,6 +18,8 @@ package io.netty.microbench.internal;
import io.netty.microbench.util.AbstractMicrobenchmark; import io.netty.microbench.util.AbstractMicrobenchmark;
import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.PlatformDependent;
import java.util.Arrays;
import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Level;
@ -28,25 +30,21 @@ import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Threads;
import java.util.Arrays;
@Threads(1) @Threads(1)
@State(Scope.Benchmark) @State(Scope.Benchmark)
public class PlatformDependentBenchmark extends AbstractMicrobenchmark { public class PlatformDependentBenchmark extends AbstractMicrobenchmark {
@Param({ "10", "50", "100", "1000", "10000", "100000" }) @Param({ "10", "50", "100", "1000", "10000", "100000" })
private String size; private int size;
private byte[] bytes1; private byte[] bytes1;
private byte[] bytes2; private byte[] bytes2;
@Setup(Level.Trial) @Setup(Level.Trial)
public void setup() { public void setup() {
int size = Integer.parseInt(this.size);
bytes1 = new byte[size]; bytes1 = new byte[size];
bytes2 = new byte[size]; bytes2 = new byte[size];
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
bytes1[i] = (byte) i; bytes1[i] = bytes2[i] = (byte) i;
bytes2[i] = (byte) i;
} }
} }