From f1e122a0c1158b379fc040ef013fe31e707628ac Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Wed, 15 Apr 2015 13:55:42 -0700 Subject: [PATCH] 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[]. --- .../java/io/netty/buffer/ByteBufUtil.java | 4 +- .../http2/DefaultHttp2HeadersEncoder.java | 5 +- .../netty/handler/codec/http2/HttpUtil.java | 2 +- .../main/java/io/netty/util/AsciiString.java | 277 ++++++------- .../main/java/io/netty/util/ByteString.java | 386 ++++++++++-------- .../util/internal/PlatformDependent.java | 5 +- common/src/main/script/codegen.groovy | 2 +- .../java/io/netty/util/AsciiStringTest.java | 23 +- .../java/io/netty/util/ByteStringTest.java | 204 +++++++++ .../internal/PlatformDependentBenchmark.java | 10 +- 10 files changed, 592 insertions(+), 326 deletions(-) create mode 100644 common/src/test/java/io/netty/util/ByteStringTest.java diff --git a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java index fbbe2b1c0f..b8e0c0679b 100644 --- a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java +++ b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java @@ -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 { diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java index b619606cc5..28be6e898f 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java @@ -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)); } /** diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java index f1c3bff65c..066f3032a8 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java @@ -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; diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index 2e3d3d6918..9878126cd4 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -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 res = new ArrayList(); 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); - } } diff --git a/common/src/main/java/io/netty/util/ByteString.java b/common/src/main/java/io/netty/util/ByteString.java index 5511ed09ba..f1e313eecb 100644 --- a/common/src/main/java/io/netty/util/ByteString.java +++ b/common/src/main/java/io/netty/util/ByteString.java @@ -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); } } diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent.java b/common/src/main/java/io/netty/util/internal/PlatformDependent.java index 08690612d8..9d0eec8047 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java @@ -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; } } diff --git a/common/src/main/script/codegen.groovy b/common/src/main/script/codegen.groovy index fc7ee83680..0fd512a4f5 100644 --- a/common/src/main/script/codegen.groovy +++ b/common/src/main/script/codegen.groovy @@ -23,7 +23,7 @@ void convertTemplates(String templateDir, def keyName = keyPrimitive.capitalize() def replaceFrom = "(^.*)K([^.]+)\\.template\$" 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) { fileset(dir: templateDir) { include(name: "**/*.template") diff --git a/common/src/test/java/io/netty/util/AsciiStringTest.java b/common/src/test/java/io/netty/util/AsciiStringTest.java index f21b046245..7b12b06fd6 100644 --- a/common/src/test/java/io/netty/util/AsciiStringTest.java +++ b/common/src/test/java/io/netty/util/AsciiStringTest.java @@ -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)); + } } diff --git a/common/src/test/java/io/netty/util/ByteStringTest.java b/common/src/test/java/io/netty/util/ByteStringTest.java new file mode 100644 index 0000000000..1d7f786d06 --- /dev/null +++ b/common/src/test/java/io/netty/util/ByteStringTest.java @@ -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 aCount = new AtomicReference(0); + final AtomicReference bCount = new AtomicReference(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 aCount = new AtomicReference(0); + final AtomicReference bCount = new AtomicReference(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); + } +} diff --git a/microbench/src/main/java/io/netty/microbench/internal/PlatformDependentBenchmark.java b/microbench/src/main/java/io/netty/microbench/internal/PlatformDependentBenchmark.java index be16f6cb27..105298a815 100644 --- a/microbench/src/main/java/io/netty/microbench/internal/PlatformDependentBenchmark.java +++ b/microbench/src/main/java/io/netty/microbench/internal/PlatformDependentBenchmark.java @@ -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; } }