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:
parent
70a2608325
commit
f812180c2d
@ -610,7 +610,7 @@ public final class ByteBufUtil {
|
||||
+ 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 + ')');
|
||||
}
|
||||
|
||||
checkNotNull(dst, "dst").writeBytes(src.array(), srcIdx, length);
|
||||
checkNotNull(dst, "dst").writeBytes(src.array(), srcIdx + src.arrayOffset(), length);
|
||||
}
|
||||
|
||||
static final class ThreadLocalUnsafeDirectByteBuf extends UnpooledUnsafeDirectByteBuf {
|
||||
|
@ -109,7 +109,10 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -377,7 +377,7 @@ public final class HttpUtil {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Invalid HTTP/2 header '%s' encountered in translation to HTTP/1.x", translatedName);
|
||||
} 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;
|
||||
|
@ -24,7 +24,6 @@ import io.netty.util.internal.PlatformDependent;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
@ -69,11 +68,10 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
int length2 = o2.length();
|
||||
int minLength = Math.min(length1, length2);
|
||||
if (a1 != null && a2 != null) {
|
||||
byte[] thisValue = a1.value;
|
||||
byte[] thatValue = a2.value;
|
||||
for (int i = 0; i < minLength; i++) {
|
||||
byte v1 = thisValue[i];
|
||||
byte v2 = thatValue[i];
|
||||
final int a1Len = minLength + a1.arrayOffset();
|
||||
for (int i = a1.arrayOffset(), j = a2.arrayOffset(); i < a1Len; i++, j++) {
|
||||
byte v1 = a1.value[i];
|
||||
byte v2 = a2.value[j];
|
||||
if (v1 == v2) {
|
||||
continue;
|
||||
}
|
||||
@ -85,20 +83,18 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
}
|
||||
}
|
||||
} else if (a1 != null) {
|
||||
byte[] thisValue = a1.value;
|
||||
for (int i = 0; i < minLength; i++) {
|
||||
int c1 = toLowerCase(thisValue[i]);
|
||||
int c2 = toLowerCase(o2.charAt(i));
|
||||
for (int i = a1.arrayOffset(), j = 0; j < minLength; i++, j++) {
|
||||
int c1 = toLowerCase(a1.value[i]);
|
||||
int c2 = toLowerCase(o2.charAt(j));
|
||||
result = c1 - c2;
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else if (a2 != null) {
|
||||
byte[] thatValue = a2.value;
|
||||
for (int i = 0; i < minLength; i++) {
|
||||
for (int i = 0, j = a2.arrayOffset(); i < minLength; i++, j++) {
|
||||
int c1 = toLowerCase(o1.charAt(i));
|
||||
int c2 = toLowerCase(thatValue[i]);
|
||||
int c2 = toLowerCase(a2.value[j]);
|
||||
result = c1 - c2;
|
||||
if (result != 0) {
|
||||
return result;
|
||||
@ -134,31 +130,28 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
int length2 = o2.length();
|
||||
int minLength = Math.min(length1, length2);
|
||||
if (a1 != null && a2 != null) {
|
||||
byte[] thisValue = a1.value;
|
||||
byte[] thatValue = a2.value;
|
||||
for (int i = 0; i < minLength; i++) {
|
||||
byte v1 = thisValue[i];
|
||||
byte v2 = thatValue[i];
|
||||
final int a1Len = minLength + a1.arrayOffset();
|
||||
for (int i = a1.arrayOffset(), j = a2.arrayOffset(); i < a1Len; i++, j++) {
|
||||
byte v1 = a1.value[i];
|
||||
byte v2 = a2.value[j];
|
||||
result = v1 - v2;
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else if (a1 != null) {
|
||||
byte[] thisValue = a1.value;
|
||||
for (int i = 0; i < minLength; i++) {
|
||||
int c1 = thisValue[i];
|
||||
int c2 = o2.charAt(i);
|
||||
for (int i = a1.arrayOffset(), j = 0; j < minLength; i++, j++) {
|
||||
int c1 = a1.value[i];
|
||||
int c2 = o2.charAt(j);
|
||||
result = c1 - c2;
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else if (a2 != null) {
|
||||
byte[] thatValue = a2.value;
|
||||
for (int i = 0; i < minLength; i++) {
|
||||
for (int i = 0, j = a2.arrayOffset(); i < minLength; i++, j++) {
|
||||
int c1 = o1.charAt(i);
|
||||
int c2 = thatValue[i];
|
||||
int c2 = a2.value[j];
|
||||
result = c1 - c2;
|
||||
if (result != 0) {
|
||||
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
|
||||
* 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) {
|
||||
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;
|
||||
final int end = value.length();
|
||||
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;
|
||||
}
|
||||
|
||||
@ -267,20 +287,16 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
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) {
|
||||
super(value, start, length, copy);
|
||||
}
|
||||
|
||||
public AsciiString(ByteBuffer value) {
|
||||
super(value);
|
||||
public AsciiString(ByteString value, boolean copy) {
|
||||
super(value, copy);
|
||||
}
|
||||
|
||||
public AsciiString(ByteBuffer value, int start, int length) {
|
||||
super(value, start, length);
|
||||
public AsciiString(ByteBuffer value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
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() + ')');
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
this.value[i] = c2b(value.charAt(start + i));
|
||||
for (int i = 0, j = start; i < length; i++, j++) {
|
||||
this.value[i] = c2b(value.charAt(j));
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,6 +340,12 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return b2c(byteAt(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arrayChanged() {
|
||||
string = null;
|
||||
super.arrayChanged();
|
||||
}
|
||||
|
||||
private static byte c2b(char c) {
|
||||
if (c > MAX_CHAR_VALUE) {
|
||||
return '?';
|
||||
@ -381,6 +403,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
* positive integer if this string is after the specified string.
|
||||
* @throws NullPointerException if {@code string} is {@code null}.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(CharSequence string) {
|
||||
if (this == string) {
|
||||
return 0;
|
||||
@ -390,9 +413,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
int length1 = length();
|
||||
int length2 = string.length();
|
||||
int minLength = Math.min(length1, length2);
|
||||
byte[] value = this.value;
|
||||
for (int i = 0, j = 0; j < minLength; i++, j++) {
|
||||
result = b2c(value[i]) - string.charAt(j);
|
||||
for (int i = 0, j = arrayOffset(); i < minLength; i++, j++) {
|
||||
result = b2c(value[j]) - string.charAt(i);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
@ -438,9 +460,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return that;
|
||||
}
|
||||
|
||||
byte[] newValue = Arrays.copyOf(value, thisLen + thatLen);
|
||||
System.arraycopy(that.value, 0, newValue, thisLen, thatLen);
|
||||
|
||||
byte[] newValue = new byte[thisLen + thatLen];
|
||||
System.arraycopy(value, arrayOffset(), newValue, 0, thisLen);
|
||||
System.arraycopy(that.value, that.arrayOffset(), newValue, thisLen, thatLen);
|
||||
return new AsciiString(newValue, false);
|
||||
}
|
||||
|
||||
@ -448,9 +470,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return new AsciiString(string);
|
||||
}
|
||||
|
||||
int newLen = thisLen + thatLen;
|
||||
byte[] newValue = Arrays.copyOf(value, newLen);
|
||||
for (int i = thisLen, j = 0; i < newLen; i++, j++) {
|
||||
byte[] newValue = new byte[thisLen + thatLen];
|
||||
System.arraycopy(value, arrayOffset(), newValue, 0, thisLen);
|
||||
for (int i = thisLen, j = 0; i < newValue.length; i++, j++) {
|
||||
newValue[i] = c2b(string.charAt(j));
|
||||
}
|
||||
|
||||
@ -491,8 +513,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < thisLen; i++) {
|
||||
char c1 = b2c(value[i]);
|
||||
for (int i = 0, j = arrayOffset(); i < thisLen; i++, j++) {
|
||||
char c1 = b2c(value[j]);
|
||||
char c2 = string.charAt(i);
|
||||
if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) {
|
||||
return false;
|
||||
@ -521,8 +543,13 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
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];
|
||||
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]);
|
||||
}
|
||||
return buffer;
|
||||
@ -541,22 +568,30 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
|
||||
final int thisLen = value.length;
|
||||
|
||||
if (srcIdx < 0 || length > thisLen - srcIdx) {
|
||||
if (srcIdx < 0 || length > length() - srcIdx) {
|
||||
throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
|
||||
+ length + ") <= srcLen(" + thisLen + ')');
|
||||
+ length + ") <= srcLen(" + length() + ')');
|
||||
}
|
||||
|
||||
final int dstEnd = dstIdx + length;
|
||||
for (int i = srcIdx, j = dstIdx; j < dstEnd; i++, j++) {
|
||||
dst[j] = b2c(value[i]);
|
||||
for (int i = dstIdx, j = srcIdx + arrayOffset(); i < dstEnd; i++, j++) {
|
||||
dst[i] = b2c(value[j]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsciiString subSequence(int start) {
|
||||
return subSequence(start, length());
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
final int thisLen = value.length;
|
||||
final int thisLen = length();
|
||||
|
||||
int subCount = subString.length();
|
||||
if (subCount <= 0) {
|
||||
@ -598,6 +633,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
}
|
||||
|
||||
final char firstChar = subString.charAt(0);
|
||||
if (firstChar > MAX_CHAR_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
ByteProcessor IndexOfVisitor = new IndexOfProcessor((byte) firstChar);
|
||||
try {
|
||||
for (;;) {
|
||||
@ -606,7 +644,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return -1; // handles subCount > count || start >= count
|
||||
}
|
||||
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
|
||||
}
|
||||
if (o2 == subCount) {
|
||||
@ -645,7 +683,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
* @throws NullPointerException if {@code subString} is {@code null}.
|
||||
*/
|
||||
public int lastIndexOf(CharSequence subString, int start) {
|
||||
final int thisLen = value.length;
|
||||
final int thisLen = length();
|
||||
final int subCount = subString.length();
|
||||
|
||||
if (subCount > thisLen || start < 0) {
|
||||
@ -660,6 +698,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
|
||||
// count and subCount are both >= 1
|
||||
final char firstChar = subString.charAt(0);
|
||||
if (firstChar > MAX_CHAR_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
ByteProcessor IndexOfVisitor = new IndexOfProcessor((byte) firstChar);
|
||||
try {
|
||||
for (;;) {
|
||||
@ -668,7 +709,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return -1;
|
||||
}
|
||||
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
|
||||
}
|
||||
if (o2 == subCount) {
|
||||
@ -702,7 +743,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return false;
|
||||
}
|
||||
|
||||
final int thisLen = value.length;
|
||||
final int thisLen = length();
|
||||
if (thisStart < 0 || thisLen - thisStart < length) {
|
||||
return false;
|
||||
}
|
||||
@ -711,9 +752,9 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return true;
|
||||
}
|
||||
|
||||
final int thisEnd = thisStart + length;
|
||||
for (int i = thisStart, j = start; i < thisEnd; i++, j++) {
|
||||
if (b2c(value[i]) != string.charAt(j)) {
|
||||
final int thatEnd = start + length;
|
||||
for (int i = start, j = thisStart + arrayOffset(); i < thatEnd; i++, j++) {
|
||||
if (b2c(value[j]) != string.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -741,7 +782,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
throw new NullPointerException("string");
|
||||
}
|
||||
|
||||
final int thisLen = value.length;
|
||||
final int thisLen = length();
|
||||
if (thisStart < 0 || length > thisLen - thisStart) {
|
||||
return false;
|
||||
}
|
||||
@ -749,7 +790,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return false;
|
||||
}
|
||||
|
||||
int thisEnd = thisStart + length;
|
||||
thisStart += arrayOffset();
|
||||
final int thisEnd = thisStart + length;
|
||||
while (thisStart < thisEnd) {
|
||||
char c1 = b2c(value[thisStart++]);
|
||||
char c2 = string.charAt(start++);
|
||||
@ -784,15 +826,14 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return this;
|
||||
}
|
||||
|
||||
final int count = value.length;
|
||||
final byte newCharByte = c2b(newChar);
|
||||
byte[] buffer = new byte[count];
|
||||
for (int i = 0, j = 0; i < count; i++, j++) {
|
||||
byte b = value[i];
|
||||
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;
|
||||
}
|
||||
buffer[j] = b;
|
||||
buffer[i] = b;
|
||||
}
|
||||
|
||||
return new AsciiString(buffer, false);
|
||||
@ -831,7 +872,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
public AsciiString toLowerCase() {
|
||||
boolean lowercased = true;
|
||||
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];
|
||||
if (b >= 'A' && b <= 'Z') {
|
||||
lowercased = false;
|
||||
@ -844,9 +886,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return this;
|
||||
}
|
||||
|
||||
final int length = value.length;
|
||||
final byte[] newValue = new byte[length];
|
||||
for (i = 0, j = 0; i < length; ++i, ++j) {
|
||||
final byte[] newValue = new byte[length()];
|
||||
for (i = 0, j = arrayOffset(); i < newValue.length; ++i, ++j) {
|
||||
newValue[i] = toLowerCase(value[j]);
|
||||
}
|
||||
|
||||
@ -861,7 +902,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
public AsciiString toUpperCase() {
|
||||
boolean uppercased = true;
|
||||
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];
|
||||
if (b >= 'a' && b <= 'z') {
|
||||
uppercased = false;
|
||||
@ -874,9 +916,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
return this;
|
||||
}
|
||||
|
||||
final int length = value.length;
|
||||
final byte[] newValue = new byte[length];
|
||||
for (i = 0, j = 0; i < length; ++i, ++j) {
|
||||
final byte[] newValue = new byte[length()];
|
||||
for (i = 0, j = arrayOffset(); i < newValue.length; ++i, ++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.
|
||||
*/
|
||||
public AsciiString trim() {
|
||||
int start = 0, last = value.length;
|
||||
int start = arrayOffset(), last = arrayOffset() + length();
|
||||
int end = last;
|
||||
while (start <= end && value[start] <= ' ') {
|
||||
start++;
|
||||
@ -969,13 +1010,13 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
final List<AsciiString> res = new ArrayList<AsciiString>();
|
||||
|
||||
int start = 0;
|
||||
final int length = value.length;
|
||||
final int length = length();
|
||||
for (int i = start; i < length; i++) {
|
||||
if (charAt(i) == delim) {
|
||||
if (start == i) {
|
||||
res.add(EMPTY_STRING);
|
||||
} else {
|
||||
res.add(new AsciiString(value, start, i - start, false));
|
||||
res.add(new AsciiString(value, start + arrayOffset(), i - start, false));
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
@ -986,7 +1027,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
} else {
|
||||
if (start != length) {
|
||||
// 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 {
|
||||
// Truncate trailing empty elements.
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ public class ByteString {
|
||||
int length1 = o1.length();
|
||||
int length2 = o2.length();
|
||||
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];
|
||||
if (result != 0) {
|
||||
return result;
|
||||
@ -57,13 +57,41 @@ public class ByteString {
|
||||
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);
|
||||
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()}.
|
||||
*/
|
||||
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()}.
|
||||
*/
|
||||
@ -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.
|
||||
*/
|
||||
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.
|
||||
@ -86,24 +118,13 @@ public class ByteString {
|
||||
* {@code copy} determines if a copy is made or the array is shared.
|
||||
*/
|
||||
public ByteString(byte[] value, boolean copy) {
|
||||
if (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);
|
||||
this(value, 0, checkNotNull(value, "value").length, copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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) {
|
||||
if (start < 0 || start > checkNotNull(value, "value").length - length) {
|
||||
@ -111,10 +132,34 @@ public class ByteString {
|
||||
+ ") <= " + "value.length(" + value.length + ')');
|
||||
}
|
||||
|
||||
if (copy || start != 0 || length != value.length) {
|
||||
if (copy) {
|
||||
this.value = Arrays.copyOfRange(value, start, start + length);
|
||||
offset = 0;
|
||||
this.length = length;
|
||||
} else {
|
||||
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.
|
||||
*/
|
||||
public ByteString(ByteBuffer value) {
|
||||
this.value = getBytes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
this(value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a {@link ByteString} based upon the underlying storage from {@link value}.
|
||||
* 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.
|
||||
* 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, 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) {
|
||||
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}.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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) {
|
||||
throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
|
||||
+ ") <= " + "length(" + length + ')');
|
||||
@ -239,22 +235,24 @@ public class ByteString {
|
||||
CharsetEncoder encoder = CharsetUtil.getEncoder(charset);
|
||||
ByteBuffer nativeBuffer = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * length));
|
||||
encoder.encode(cbuf, nativeBuffer, true);
|
||||
final int offset = nativeBuffer.arrayOffset();
|
||||
return Arrays.copyOfRange(nativeBuffer.array(), offset, offset + nativeBuffer.position());
|
||||
final int bufferOffset = nativeBuffer.arrayOffset();
|
||||
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) {
|
||||
return getBytes(value, charset, 0, checkNotNull(value, "value").length());
|
||||
public ByteString(CharSequence value, Charset charset) {
|
||||
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.
|
||||
*/
|
||||
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) {
|
||||
throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
|
||||
+ ") <= " + "length(" + value.length() + ')');
|
||||
@ -265,7 +263,9 @@ public class ByteString {
|
||||
ByteBuffer nativeBuffer = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * length));
|
||||
encoder.encode(cbuf, nativeBuffer, true);
|
||||
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}.
|
||||
*/
|
||||
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}.
|
||||
*/
|
||||
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
|
||||
+ ") <= " + "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])) {
|
||||
return i;
|
||||
return i - offset;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
@ -317,36 +318,42 @@ public class ByteString {
|
||||
* 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 {
|
||||
if (index < 0 || length > value.length - index) {
|
||||
if (index < 0 || length > length() - index) {
|
||||
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])) {
|
||||
return i;
|
||||
return i - offset;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
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() {
|
||||
return value.length == 0;
|
||||
return length == 0;
|
||||
}
|
||||
|
||||
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,
|
||||
* and changes then this needs to be called.
|
||||
*/
|
||||
public final void arrayChanged() {
|
||||
public void arrayChanged() {
|
||||
hash = 0;
|
||||
}
|
||||
|
||||
@ -354,11 +361,30 @@ public class ByteString {
|
||||
* This gives direct access to the underlying storage array.
|
||||
* The {@link #toByteArray()} should be preferred over this method.
|
||||
* If the return value is changed then {@link #arrayChanged()} must be called.
|
||||
* @see #arrayOffset()
|
||||
* @see #isEntireArrayUsed()
|
||||
*/
|
||||
public final byte[] array() {
|
||||
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.
|
||||
*/
|
||||
@ -371,7 +397,7 @@ public class ByteString {
|
||||
* The subset is defined by the range [{@code start}, {@code 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.
|
||||
*/
|
||||
public final void copy(int srcIdx, byte[] dst, int dstIdx, int length) {
|
||||
final int thisLen = value.length;
|
||||
|
||||
if (srcIdx < 0 || length > thisLen - srcIdx) {
|
||||
if (srcIdx < 0 || length > length() - srcIdx) {
|
||||
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
|
||||
public int hashCode() {
|
||||
int h = hash;
|
||||
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;
|
||||
}
|
||||
|
||||
hash = h;
|
||||
}
|
||||
return h;
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a range of characters into a new string.
|
||||
*
|
||||
* @param start the offset of the first character.
|
||||
* @param start the offset of the first character (inclusive).
|
||||
* @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 final ByteString subSequence(int start) {
|
||||
public ByteString subSequence(int start) {
|
||||
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) {
|
||||
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()) {
|
||||
throw new IndexOutOfBoundsException("expected: 0 <= start(" + start + ") <= end (" + end + ") <= length("
|
||||
+ length() + ')');
|
||||
}
|
||||
|
||||
if (start == 0 && end == value.length) {
|
||||
if (start == 0 && end == length()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -431,7 +489,7 @@ public class ByteString {
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
return new ByteString(value, start, end - start, false);
|
||||
return factory.newInstance(value, start + offset, end - start, copy);
|
||||
}
|
||||
|
||||
public final int parseAsciiInt() {
|
||||
@ -458,7 +516,7 @@ public class ByteString {
|
||||
int i = start;
|
||||
boolean negative = byteAt(i) == '-';
|
||||
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);
|
||||
@ -467,25 +525,25 @@ public class ByteString {
|
||||
private int parseAsciiInt(int start, int end, int radix, boolean negative) {
|
||||
int max = Integer.MIN_VALUE / radix;
|
||||
int result = 0;
|
||||
int offset = start;
|
||||
while (offset < end) {
|
||||
int digit = Character.digit((char) (value[offset++] & 0xFF), radix);
|
||||
int currOffset = start;
|
||||
while (currOffset < end) {
|
||||
int digit = Character.digit((char) (value[currOffset++ + offset] & 0xFF), radix);
|
||||
if (digit == -1) {
|
||||
throw new NumberFormatException(subSequence(start, end).toString());
|
||||
throw new NumberFormatException(subSequence(start, end, false).toString());
|
||||
}
|
||||
if (max > result) {
|
||||
throw new NumberFormatException(subSequence(start, end).toString());
|
||||
throw new NumberFormatException(subSequence(start, end, false).toString());
|
||||
}
|
||||
int next = result * radix - digit;
|
||||
if (next > result) {
|
||||
throw new NumberFormatException(subSequence(start, end).toString());
|
||||
throw new NumberFormatException(subSequence(start, end, false).toString());
|
||||
}
|
||||
result = next;
|
||||
}
|
||||
if (!negative) {
|
||||
result = -result;
|
||||
if (result < 0) {
|
||||
throw new NumberFormatException(subSequence(start, end).toString());
|
||||
throw new NumberFormatException(subSequence(start, end, false).toString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -515,7 +573,7 @@ public class ByteString {
|
||||
int i = start;
|
||||
boolean negative = byteAt(i) == '-';
|
||||
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);
|
||||
@ -524,25 +582,25 @@ public class ByteString {
|
||||
private long parseAsciiLong(int start, int end, int radix, boolean negative) {
|
||||
long max = Long.MIN_VALUE / radix;
|
||||
long result = 0;
|
||||
int offset = start;
|
||||
while (offset < end) {
|
||||
int digit = Character.digit((char) (value[offset++] & 0xFF), radix);
|
||||
int currOffset = start;
|
||||
while (currOffset < end) {
|
||||
int digit = Character.digit((char) (value[currOffset++ + offset] & 0xFF), radix);
|
||||
if (digit == -1) {
|
||||
throw new NumberFormatException(subSequence(start, end).toString());
|
||||
throw new NumberFormatException(subSequence(start, end, false).toString());
|
||||
}
|
||||
if (max > result) {
|
||||
throw new NumberFormatException(subSequence(start, end).toString());
|
||||
throw new NumberFormatException(subSequence(start, end, false).toString());
|
||||
}
|
||||
long next = result * radix - digit;
|
||||
if (next > result) {
|
||||
throw new NumberFormatException(subSequence(start, end).toString());
|
||||
throw new NumberFormatException(subSequence(start, end, false).toString());
|
||||
}
|
||||
result = next;
|
||||
}
|
||||
if (!negative) {
|
||||
result = -result;
|
||||
if (result < 0) {
|
||||
throw new NumberFormatException(subSequence(start, end).toString());
|
||||
throw new NumberFormatException(subSequence(start, end, false).toString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -553,11 +611,12 @@ public class ByteString {
|
||||
}
|
||||
|
||||
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 " +
|
||||
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() {
|
||||
@ -576,7 +635,7 @@ public class ByteString {
|
||||
int intValue = parseAsciiInt(start, end, radix);
|
||||
short result = (short) intValue;
|
||||
if (result != intValue) {
|
||||
throw new NumberFormatException(subSequence(start, end).toString());
|
||||
throw new NumberFormatException(subSequence(start, end, false).toString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -605,9 +664,11 @@ public class ByteString {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ByteString other = (ByteString) obj;
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
@ -863,8 +863,9 @@ public final class PlatformDependent {
|
||||
if (len1 != len2) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < len1; i++) {
|
||||
if (bytes1[startPos1 + i] != bytes2[startPos2 + i]) {
|
||||
final int end = startPos1 + len1;
|
||||
for (int i = startPos1, j = startPos2; i < end; ++i, ++j) {
|
||||
if (bytes1[i] != bytes2[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.netty.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
@ -26,7 +27,6 @@ import org.junit.Test;
|
||||
* Test for the {@link AsciiString} class
|
||||
*/
|
||||
public class AsciiStringTest {
|
||||
|
||||
@Test
|
||||
public void testGetBytesStringBuilder() {
|
||||
final StringBuilder b = new StringBuilder();
|
||||
@ -78,4 +78,25 @@ public class AsciiStringTest {
|
||||
AsciiString ascii = new AsciiString(string.toCharArray());
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
204
common/src/test/java/io/netty/util/ByteStringTest.java
Normal file
204
common/src/test/java/io/netty/util/ByteStringTest.java
Normal 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);
|
||||
}
|
||||
}
|
@ -18,6 +18,8 @@ package io.netty.microbench.internal;
|
||||
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
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.Threads;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Threads(1)
|
||||
@State(Scope.Benchmark)
|
||||
public class PlatformDependentBenchmark extends AbstractMicrobenchmark {
|
||||
|
||||
@Param({ "10", "50", "100", "1000", "10000", "100000" })
|
||||
private String size;
|
||||
private int size;
|
||||
private byte[] bytes1;
|
||||
private byte[] bytes2;
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() {
|
||||
int size = Integer.parseInt(this.size);
|
||||
bytes1 = new byte[size];
|
||||
bytes2 = new byte[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
bytes1[i] = (byte) i;
|
||||
bytes2[i] = (byte) i;
|
||||
bytes1[i] = bytes2[i] = (byte) i;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user