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 + ')');
|
+ 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 {
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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.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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user