1440 lines
43 KiB
Java
1440 lines
43 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
/*
|
|
* Copyright (C) 2014 Square, Inc.
|
|
*
|
|
* Licensed 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 com.android.okhttp.okio;
|
|
|
|
import java.io.EOFException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.nio.charset.Charset;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
import static com.android.okhttp.okio.Util.checkOffsetAndCount;
|
|
import static com.android.okhttp.okio.Util.reverseBytesLong;
|
|
|
|
/**
|
|
* A collection of bytes in memory.
|
|
*
|
|
* <p><strong>Moving data from one buffer to another is fast.</strong> Instead
|
|
* of copying bytes from one place in memory to another, this class just changes
|
|
* ownership of the underlying byte arrays.
|
|
*
|
|
* <p><strong>This buffer grows with your data.</strong> Just like ArrayList,
|
|
* each buffer starts small. It consumes only the memory it needs to.
|
|
*
|
|
* <p><strong>This buffer pools its byte arrays.</strong> When you allocate a
|
|
* byte array in Java, the runtime must zero-fill the requested array before
|
|
* returning it to you. Even if you're going to write over that space anyway.
|
|
* This class avoids zero-fill and GC churn by pooling byte arrays.
|
|
* @hide This class is not part of the Android public SDK API
|
|
*/
|
|
public final class Buffer implements BufferedSource, BufferedSink, Cloneable {
|
|
private static final byte[] DIGITS =
|
|
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
|
static final int REPLACEMENT_CHARACTER = '\ufffd';
|
|
|
|
Segment head;
|
|
long size;
|
|
|
|
public Buffer() {
|
|
}
|
|
|
|
/** Returns the number of bytes currently in this buffer. */
|
|
public long size() {
|
|
return size;
|
|
}
|
|
|
|
@Override public Buffer buffer() {
|
|
return this;
|
|
}
|
|
|
|
@Override public OutputStream outputStream() {
|
|
return new OutputStream() {
|
|
@Override public void write(int b) {
|
|
writeByte((byte) b);
|
|
}
|
|
|
|
@Override public void write(byte[] data, int offset, int byteCount) {
|
|
Buffer.this.write(data, offset, byteCount);
|
|
}
|
|
|
|
@Override public void flush() {
|
|
}
|
|
|
|
@Override public void close() {
|
|
}
|
|
|
|
@Override public String toString() {
|
|
return this + ".outputStream()";
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override public Buffer emitCompleteSegments() {
|
|
return this; // Nowhere to emit to!
|
|
}
|
|
|
|
@Override public BufferedSink emit() {
|
|
return this; // Nowhere to emit to!
|
|
}
|
|
|
|
@Override public boolean exhausted() {
|
|
return size == 0;
|
|
}
|
|
|
|
@Override public void require(long byteCount) throws EOFException {
|
|
if (size < byteCount) throw new EOFException();
|
|
}
|
|
|
|
@Override public boolean request(long byteCount) {
|
|
return size >= byteCount;
|
|
}
|
|
|
|
@Override public InputStream inputStream() {
|
|
return new InputStream() {
|
|
@Override public int read() {
|
|
if (size > 0) return readByte() & 0xff;
|
|
return -1;
|
|
}
|
|
|
|
@Override public int read(byte[] sink, int offset, int byteCount) {
|
|
return Buffer.this.read(sink, offset, byteCount);
|
|
}
|
|
|
|
@Override public int available() {
|
|
return (int) Math.min(size, Integer.MAX_VALUE);
|
|
}
|
|
|
|
@Override public void close() {
|
|
}
|
|
|
|
@Override public String toString() {
|
|
return Buffer.this + ".inputStream()";
|
|
}
|
|
};
|
|
}
|
|
|
|
/** Copy the contents of this to {@code out}. */
|
|
public Buffer copyTo(OutputStream out) throws IOException {
|
|
return copyTo(out, 0, size);
|
|
}
|
|
|
|
/**
|
|
* Copy {@code byteCount} bytes from this, starting at {@code offset}, to
|
|
* {@code out}.
|
|
*/
|
|
public Buffer copyTo(OutputStream out, long offset, long byteCount) throws IOException {
|
|
if (out == null) throw new IllegalArgumentException("out == null");
|
|
checkOffsetAndCount(size, offset, byteCount);
|
|
if (byteCount == 0) return this;
|
|
|
|
// Skip segments that we aren't copying from.
|
|
Segment s = head;
|
|
for (; offset >= (s.limit - s.pos); s = s.next) {
|
|
offset -= (s.limit - s.pos);
|
|
}
|
|
|
|
// Copy from one segment at a time.
|
|
for (; byteCount > 0; s = s.next) {
|
|
int pos = (int) (s.pos + offset);
|
|
int toCopy = (int) Math.min(s.limit - pos, byteCount);
|
|
out.write(s.data, pos, toCopy);
|
|
byteCount -= toCopy;
|
|
offset = 0;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/** Copy {@code byteCount} bytes from this, starting at {@code offset}, to {@code out}. */
|
|
public Buffer copyTo(Buffer out, long offset, long byteCount) {
|
|
if (out == null) throw new IllegalArgumentException("out == null");
|
|
checkOffsetAndCount(size, offset, byteCount);
|
|
if (byteCount == 0) return this;
|
|
|
|
out.size += byteCount;
|
|
|
|
// Skip segments that we aren't copying from.
|
|
Segment s = head;
|
|
for (; offset >= (s.limit - s.pos); s = s.next) {
|
|
offset -= (s.limit - s.pos);
|
|
}
|
|
|
|
// Copy one segment at a time.
|
|
for (; byteCount > 0; s = s.next) {
|
|
Segment copy = new Segment(s);
|
|
copy.pos += offset;
|
|
copy.limit = Math.min(copy.pos + (int) byteCount, copy.limit);
|
|
if (out.head == null) {
|
|
out.head = copy.next = copy.prev = copy;
|
|
} else {
|
|
out.head.prev.push(copy);
|
|
}
|
|
byteCount -= copy.limit - copy.pos;
|
|
offset = 0;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/** Write the contents of this to {@code out}. */
|
|
public Buffer writeTo(OutputStream out) throws IOException {
|
|
return writeTo(out, size);
|
|
}
|
|
|
|
/** Write {@code byteCount} bytes from this to {@code out}. */
|
|
public Buffer writeTo(OutputStream out, long byteCount) throws IOException {
|
|
if (out == null) throw new IllegalArgumentException("out == null");
|
|
checkOffsetAndCount(size, 0, byteCount);
|
|
|
|
Segment s = head;
|
|
while (byteCount > 0) {
|
|
int toCopy = (int) Math.min(byteCount, s.limit - s.pos);
|
|
out.write(s.data, s.pos, toCopy);
|
|
|
|
s.pos += toCopy;
|
|
size -= toCopy;
|
|
byteCount -= toCopy;
|
|
|
|
if (s.pos == s.limit) {
|
|
Segment toRecycle = s;
|
|
head = s = toRecycle.pop();
|
|
SegmentPool.recycle(toRecycle);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/** Read and exhaust bytes from {@code in} to this. */
|
|
public Buffer readFrom(InputStream in) throws IOException {
|
|
readFrom(in, Long.MAX_VALUE, true);
|
|
return this;
|
|
}
|
|
|
|
/** Read {@code byteCount} bytes from {@code in} to this. */
|
|
public Buffer readFrom(InputStream in, long byteCount) throws IOException {
|
|
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
|
|
readFrom(in, byteCount, false);
|
|
return this;
|
|
}
|
|
|
|
private void readFrom(InputStream in, long byteCount, boolean forever) throws IOException {
|
|
if (in == null) throw new IllegalArgumentException("in == null");
|
|
while (byteCount > 0 || forever) {
|
|
Segment tail = writableSegment(1);
|
|
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
|
|
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
|
|
if (bytesRead == -1) {
|
|
if (forever) return;
|
|
throw new EOFException();
|
|
}
|
|
tail.limit += bytesRead;
|
|
size += bytesRead;
|
|
byteCount -= bytesRead;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the number of bytes in segments that are not writable. This is the
|
|
* number of bytes that can be flushed immediately to an underlying sink
|
|
* without harming throughput.
|
|
*/
|
|
public long completeSegmentByteCount() {
|
|
long result = size;
|
|
if (result == 0) return 0;
|
|
|
|
// Omit the tail if it's still writable.
|
|
Segment tail = head.prev;
|
|
if (tail.limit < Segment.SIZE && tail.owner) {
|
|
result -= tail.limit - tail.pos;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override public byte readByte() {
|
|
if (size == 0) throw new IllegalStateException("size == 0");
|
|
|
|
Segment segment = head;
|
|
int pos = segment.pos;
|
|
int limit = segment.limit;
|
|
|
|
byte[] data = segment.data;
|
|
byte b = data[pos++];
|
|
size -= 1;
|
|
|
|
if (pos == limit) {
|
|
head = segment.pop();
|
|
SegmentPool.recycle(segment);
|
|
} else {
|
|
segment.pos = pos;
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
/** Returns the byte at {@code pos}. */
|
|
public byte getByte(long pos) {
|
|
checkOffsetAndCount(size, pos, 1);
|
|
for (Segment s = head; true; s = s.next) {
|
|
int segmentByteCount = s.limit - s.pos;
|
|
if (pos < segmentByteCount) return s.data[s.pos + (int) pos];
|
|
pos -= segmentByteCount;
|
|
}
|
|
}
|
|
|
|
@Override public short readShort() {
|
|
if (size < 2) throw new IllegalStateException("size < 2: " + size);
|
|
|
|
Segment segment = head;
|
|
int pos = segment.pos;
|
|
int limit = segment.limit;
|
|
|
|
// If the short is split across multiple segments, delegate to readByte().
|
|
if (limit - pos < 2) {
|
|
int s = (readByte() & 0xff) << 8
|
|
| (readByte() & 0xff);
|
|
return (short) s;
|
|
}
|
|
|
|
byte[] data = segment.data;
|
|
int s = (data[pos++] & 0xff) << 8
|
|
| (data[pos++] & 0xff);
|
|
size -= 2;
|
|
|
|
if (pos == limit) {
|
|
head = segment.pop();
|
|
SegmentPool.recycle(segment);
|
|
} else {
|
|
segment.pos = pos;
|
|
}
|
|
|
|
return (short) s;
|
|
}
|
|
|
|
@Override public int readInt() {
|
|
if (size < 4) throw new IllegalStateException("size < 4: " + size);
|
|
|
|
Segment segment = head;
|
|
int pos = segment.pos;
|
|
int limit = segment.limit;
|
|
|
|
// If the int is split across multiple segments, delegate to readByte().
|
|
if (limit - pos < 4) {
|
|
return (readByte() & 0xff) << 24
|
|
| (readByte() & 0xff) << 16
|
|
| (readByte() & 0xff) << 8
|
|
| (readByte() & 0xff);
|
|
}
|
|
|
|
byte[] data = segment.data;
|
|
int i = (data[pos++] & 0xff) << 24
|
|
| (data[pos++] & 0xff) << 16
|
|
| (data[pos++] & 0xff) << 8
|
|
| (data[pos++] & 0xff);
|
|
size -= 4;
|
|
|
|
if (pos == limit) {
|
|
head = segment.pop();
|
|
SegmentPool.recycle(segment);
|
|
} else {
|
|
segment.pos = pos;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
@Override public long readLong() {
|
|
if (size < 8) throw new IllegalStateException("size < 8: " + size);
|
|
|
|
Segment segment = head;
|
|
int pos = segment.pos;
|
|
int limit = segment.limit;
|
|
|
|
// If the long is split across multiple segments, delegate to readInt().
|
|
if (limit - pos < 8) {
|
|
return (readInt() & 0xffffffffL) << 32
|
|
| (readInt() & 0xffffffffL);
|
|
}
|
|
|
|
byte[] data = segment.data;
|
|
long v = (data[pos++] & 0xffL) << 56
|
|
| (data[pos++] & 0xffL) << 48
|
|
| (data[pos++] & 0xffL) << 40
|
|
| (data[pos++] & 0xffL) << 32
|
|
| (data[pos++] & 0xffL) << 24
|
|
| (data[pos++] & 0xffL) << 16
|
|
| (data[pos++] & 0xffL) << 8
|
|
| (data[pos++] & 0xffL);
|
|
size -= 8;
|
|
|
|
if (pos == limit) {
|
|
head = segment.pop();
|
|
SegmentPool.recycle(segment);
|
|
} else {
|
|
segment.pos = pos;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
@Override public short readShortLe() {
|
|
return Util.reverseBytesShort(readShort());
|
|
}
|
|
|
|
@Override public int readIntLe() {
|
|
return Util.reverseBytesInt(readInt());
|
|
}
|
|
|
|
@Override public long readLongLe() {
|
|
return Util.reverseBytesLong(readLong());
|
|
}
|
|
|
|
@Override public long readDecimalLong() {
|
|
if (size == 0) throw new IllegalStateException("size == 0");
|
|
|
|
// This value is always built negatively in order to accommodate Long.MIN_VALUE.
|
|
long value = 0;
|
|
int seen = 0;
|
|
boolean negative = false;
|
|
boolean done = false;
|
|
|
|
long overflowZone = Long.MIN_VALUE / 10;
|
|
long overflowDigit = (Long.MIN_VALUE % 10) + 1;
|
|
|
|
do {
|
|
Segment segment = head;
|
|
|
|
byte[] data = segment.data;
|
|
int pos = segment.pos;
|
|
int limit = segment.limit;
|
|
|
|
for (; pos < limit; pos++, seen++) {
|
|
byte b = data[pos];
|
|
if (b >= '0' && b <= '9') {
|
|
int digit = '0' - b;
|
|
|
|
// Detect when the digit would cause an overflow.
|
|
if (value < overflowZone || value == overflowZone && digit < overflowDigit) {
|
|
Buffer buffer = new Buffer().writeDecimalLong(value).writeByte(b);
|
|
if (!negative) buffer.readByte(); // Skip negative sign.
|
|
throw new NumberFormatException("Number too large: " + buffer.readUtf8());
|
|
}
|
|
value *= 10;
|
|
value += digit;
|
|
} else if (b == '-' && seen == 0) {
|
|
negative = true;
|
|
overflowDigit -= 1;
|
|
} else {
|
|
if (seen == 0) {
|
|
throw new NumberFormatException(
|
|
"Expected leading [0-9] or '-' character but was 0x" + Integer.toHexString(b));
|
|
}
|
|
// Set a flag to stop iteration. We still need to run through segment updating below.
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pos == limit) {
|
|
head = segment.pop();
|
|
SegmentPool.recycle(segment);
|
|
} else {
|
|
segment.pos = pos;
|
|
}
|
|
} while (!done && head != null);
|
|
|
|
size -= seen;
|
|
return negative ? value : -value;
|
|
}
|
|
|
|
@Override public long readHexadecimalUnsignedLong() {
|
|
if (size == 0) throw new IllegalStateException("size == 0");
|
|
|
|
long value = 0;
|
|
int seen = 0;
|
|
boolean done = false;
|
|
|
|
do {
|
|
Segment segment = head;
|
|
|
|
byte[] data = segment.data;
|
|
int pos = segment.pos;
|
|
int limit = segment.limit;
|
|
|
|
for (; pos < limit; pos++, seen++) {
|
|
int digit;
|
|
|
|
byte b = data[pos];
|
|
if (b >= '0' && b <= '9') {
|
|
digit = b - '0';
|
|
} else if (b >= 'a' && b <= 'f') {
|
|
digit = b - 'a' + 10;
|
|
} else if (b >= 'A' && b <= 'F') {
|
|
digit = b - 'A' + 10; // We never write uppercase, but we support reading it.
|
|
} else {
|
|
if (seen == 0) {
|
|
throw new NumberFormatException(
|
|
"Expected leading [0-9a-fA-F] character but was 0x" + Integer.toHexString(b));
|
|
}
|
|
// Set a flag to stop iteration. We still need to run through segment updating below.
|
|
done = true;
|
|
break;
|
|
}
|
|
|
|
// Detect when the shift will overflow.
|
|
if ((value & 0xf000000000000000L) != 0) {
|
|
Buffer buffer = new Buffer().writeHexadecimalUnsignedLong(value).writeByte(b);
|
|
throw new NumberFormatException("Number too large: " + buffer.readUtf8());
|
|
}
|
|
|
|
value <<= 4;
|
|
value |= digit;
|
|
}
|
|
|
|
if (pos == limit) {
|
|
head = segment.pop();
|
|
SegmentPool.recycle(segment);
|
|
} else {
|
|
segment.pos = pos;
|
|
}
|
|
} while (!done && head != null);
|
|
|
|
size -= seen;
|
|
return value;
|
|
}
|
|
|
|
@Override public ByteString readByteString() {
|
|
return new ByteString(readByteArray());
|
|
}
|
|
|
|
@Override public ByteString readByteString(long byteCount) throws EOFException {
|
|
return new ByteString(readByteArray(byteCount));
|
|
}
|
|
|
|
@Override public void readFully(Buffer sink, long byteCount) throws EOFException {
|
|
if (size < byteCount) {
|
|
sink.write(this, size); // Exhaust ourselves.
|
|
throw new EOFException();
|
|
}
|
|
sink.write(this, byteCount);
|
|
}
|
|
|
|
@Override public long readAll(Sink sink) throws IOException {
|
|
long byteCount = size;
|
|
if (byteCount > 0) {
|
|
sink.write(this, byteCount);
|
|
}
|
|
return byteCount;
|
|
}
|
|
|
|
@Override public String readUtf8() {
|
|
try {
|
|
return readString(size, Util.UTF_8);
|
|
} catch (EOFException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
@Override public String readUtf8(long byteCount) throws EOFException {
|
|
return readString(byteCount, Util.UTF_8);
|
|
}
|
|
|
|
@Override public String readString(Charset charset) {
|
|
try {
|
|
return readString(size, charset);
|
|
} catch (EOFException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
@Override public String readString(long byteCount, Charset charset) throws EOFException {
|
|
checkOffsetAndCount(size, 0, byteCount);
|
|
if (charset == null) throw new IllegalArgumentException("charset == null");
|
|
if (byteCount > Integer.MAX_VALUE) {
|
|
throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
|
|
}
|
|
if (byteCount == 0) return "";
|
|
|
|
Segment s = head;
|
|
if (s.pos + byteCount > s.limit) {
|
|
// If the string spans multiple segments, delegate to readBytes().
|
|
return new String(readByteArray(byteCount), charset);
|
|
}
|
|
|
|
String result = new String(s.data, s.pos, (int) byteCount, charset);
|
|
s.pos += byteCount;
|
|
size -= byteCount;
|
|
|
|
if (s.pos == s.limit) {
|
|
head = s.pop();
|
|
SegmentPool.recycle(s);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override public String readUtf8Line() throws EOFException {
|
|
long newline = indexOf((byte) '\n');
|
|
|
|
if (newline == -1) {
|
|
return size != 0 ? readUtf8(size) : null;
|
|
}
|
|
|
|
return readUtf8Line(newline);
|
|
}
|
|
|
|
@Override public String readUtf8LineStrict() throws EOFException {
|
|
long newline = indexOf((byte) '\n');
|
|
if (newline == -1) {
|
|
Buffer data = new Buffer();
|
|
copyTo(data, 0, Math.min(32, size));
|
|
throw new EOFException("\\n not found: size=" + size()
|
|
+ " content=" + data.readByteString().hex() + "...");
|
|
}
|
|
return readUtf8Line(newline);
|
|
}
|
|
|
|
String readUtf8Line(long newline) throws EOFException {
|
|
if (newline > 0 && getByte(newline - 1) == '\r') {
|
|
// Read everything until '\r\n', then skip the '\r\n'.
|
|
String result = readUtf8((newline - 1));
|
|
skip(2);
|
|
return result;
|
|
|
|
} else {
|
|
// Read everything until '\n', then skip the '\n'.
|
|
String result = readUtf8(newline);
|
|
skip(1);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
@Override public int readUtf8CodePoint() throws EOFException {
|
|
if (size == 0) throw new EOFException();
|
|
|
|
byte b0 = getByte(0);
|
|
int codePoint;
|
|
int byteCount;
|
|
int min;
|
|
|
|
if ((b0 & 0x80) == 0) {
|
|
// 0xxxxxxx.
|
|
codePoint = b0 & 0x7f;
|
|
byteCount = 1; // 7 bits (ASCII).
|
|
min = 0x0;
|
|
|
|
} else if ((b0 & 0xe0) == 0xc0) {
|
|
// 0x110xxxxx
|
|
codePoint = b0 & 0x1f;
|
|
byteCount = 2; // 11 bits (5 + 6).
|
|
min = 0x80;
|
|
|
|
} else if ((b0 & 0xf0) == 0xe0) {
|
|
// 0x1110xxxx
|
|
codePoint = b0 & 0x0f;
|
|
byteCount = 3; // 16 bits (4 + 6 + 6).
|
|
min = 0x800;
|
|
|
|
} else if ((b0 & 0xf8) == 0xf0) {
|
|
// 0x11110xxx
|
|
codePoint = b0 & 0x07;
|
|
byteCount = 4; // 21 bits (3 + 6 + 6 + 6).
|
|
min = 0x10000;
|
|
|
|
} else {
|
|
// We expected the first byte of a code point but got something else.
|
|
skip(1);
|
|
return REPLACEMENT_CHARACTER;
|
|
}
|
|
|
|
if (size < byteCount) {
|
|
throw new EOFException("size < " + byteCount + ": " + size
|
|
+ " (to read code point prefixed 0x" + Integer.toHexString(b0) + ")");
|
|
}
|
|
|
|
// Read the continuation bytes. If we encounter a non-continuation byte, the sequence consumed
|
|
// thus far is truncated and is decoded as the replacement character. That non-continuation byte
|
|
// is left in the stream for processing by the next call to readUtf8CodePoint().
|
|
for (int i = 1; i < byteCount; i++) {
|
|
byte b = getByte(i);
|
|
if ((b & 0xc0) == 0x80) {
|
|
// 0x10xxxxxx
|
|
codePoint <<= 6;
|
|
codePoint |= b & 0x3f;
|
|
} else {
|
|
skip(i);
|
|
return REPLACEMENT_CHARACTER;
|
|
}
|
|
}
|
|
|
|
skip(byteCount);
|
|
|
|
if (codePoint > 0x10ffff) {
|
|
return REPLACEMENT_CHARACTER; // Reject code points larger than the Unicode maximum.
|
|
}
|
|
|
|
if (codePoint >= 0xd800 && codePoint <= 0xdfff) {
|
|
return REPLACEMENT_CHARACTER; // Reject partial surrogates.
|
|
}
|
|
|
|
if (codePoint < min) {
|
|
return REPLACEMENT_CHARACTER; // Reject overlong code points.
|
|
}
|
|
|
|
return codePoint;
|
|
}
|
|
|
|
@Override public byte[] readByteArray() {
|
|
try {
|
|
return readByteArray(size);
|
|
} catch (EOFException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
@Override public byte[] readByteArray(long byteCount) throws EOFException {
|
|
checkOffsetAndCount(size, 0, byteCount);
|
|
if (byteCount > Integer.MAX_VALUE) {
|
|
throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
|
|
}
|
|
|
|
byte[] result = new byte[(int) byteCount];
|
|
readFully(result);
|
|
return result;
|
|
}
|
|
|
|
@Override public int read(byte[] sink) {
|
|
return read(sink, 0, sink.length);
|
|
}
|
|
|
|
@Override public void readFully(byte[] sink) throws EOFException {
|
|
int offset = 0;
|
|
while (offset < sink.length) {
|
|
int read = read(sink, offset, sink.length - offset);
|
|
if (read == -1) throw new EOFException();
|
|
offset += read;
|
|
}
|
|
}
|
|
|
|
@Override public int read(byte[] sink, int offset, int byteCount) {
|
|
checkOffsetAndCount(sink.length, offset, byteCount);
|
|
|
|
Segment s = head;
|
|
if (s == null) return -1;
|
|
int toCopy = Math.min(byteCount, s.limit - s.pos);
|
|
System.arraycopy(s.data, s.pos, sink, offset, toCopy);
|
|
|
|
s.pos += toCopy;
|
|
size -= toCopy;
|
|
|
|
if (s.pos == s.limit) {
|
|
head = s.pop();
|
|
SegmentPool.recycle(s);
|
|
}
|
|
|
|
return toCopy;
|
|
}
|
|
|
|
/**
|
|
* Discards all bytes in this buffer. Calling this method when you're done
|
|
* with a buffer will return its segments to the pool.
|
|
*/
|
|
public void clear() {
|
|
try {
|
|
skip(size);
|
|
} catch (EOFException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
/** Discards {@code byteCount} bytes from the head of this buffer. */
|
|
@Override public void skip(long byteCount) throws EOFException {
|
|
while (byteCount > 0) {
|
|
if (head == null) throw new EOFException();
|
|
|
|
int toSkip = (int) Math.min(byteCount, head.limit - head.pos);
|
|
size -= toSkip;
|
|
byteCount -= toSkip;
|
|
head.pos += toSkip;
|
|
|
|
if (head.pos == head.limit) {
|
|
Segment toRecycle = head;
|
|
head = toRecycle.pop();
|
|
SegmentPool.recycle(toRecycle);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override public Buffer write(ByteString byteString) {
|
|
if (byteString == null) throw new IllegalArgumentException("byteString == null");
|
|
byteString.write(this);
|
|
return this;
|
|
}
|
|
|
|
@Override public Buffer writeUtf8(String string) {
|
|
return writeUtf8(string, 0, string.length());
|
|
}
|
|
|
|
@Override public Buffer writeUtf8(String string, int beginIndex, int endIndex) {
|
|
if (string == null) throw new IllegalArgumentException("string == null");
|
|
if (beginIndex < 0) throw new IllegalAccessError("beginIndex < 0: " + beginIndex);
|
|
if (endIndex < beginIndex) {
|
|
throw new IllegalArgumentException("endIndex < beginIndex: " + endIndex + " < " + beginIndex);
|
|
}
|
|
if (endIndex > string.length()) {
|
|
throw new IllegalArgumentException(
|
|
"endIndex > string.length: " + endIndex + " > " + string.length());
|
|
}
|
|
|
|
// Transcode a UTF-16 Java String to UTF-8 bytes.
|
|
for (int i = beginIndex; i < endIndex;) {
|
|
int c = string.charAt(i);
|
|
|
|
if (c < 0x80) {
|
|
Segment tail = writableSegment(1);
|
|
byte[] data = tail.data;
|
|
int segmentOffset = tail.limit - i;
|
|
int runLimit = Math.min(endIndex, Segment.SIZE - segmentOffset);
|
|
|
|
// Emit a 7-bit character with 1 byte.
|
|
data[segmentOffset + i++] = (byte) c; // 0xxxxxxx
|
|
|
|
// Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance
|
|
// improvement over independent calls to writeByte().
|
|
while (i < runLimit) {
|
|
c = string.charAt(i);
|
|
if (c >= 0x80) break;
|
|
data[segmentOffset + i++] = (byte) c; // 0xxxxxxx
|
|
}
|
|
|
|
int runSize = i + segmentOffset - tail.limit; // Equivalent to i - (previous i).
|
|
tail.limit += runSize;
|
|
size += runSize;
|
|
|
|
} else if (c < 0x800) {
|
|
// Emit a 11-bit character with 2 bytes.
|
|
writeByte(c >> 6 | 0xc0); // 110xxxxx
|
|
writeByte(c & 0x3f | 0x80); // 10xxxxxx
|
|
i++;
|
|
|
|
} else if (c < 0xd800 || c > 0xdfff) {
|
|
// Emit a 16-bit character with 3 bytes.
|
|
writeByte(c >> 12 | 0xe0); // 1110xxxx
|
|
writeByte(c >> 6 & 0x3f | 0x80); // 10xxxxxx
|
|
writeByte(c & 0x3f | 0x80); // 10xxxxxx
|
|
i++;
|
|
|
|
} else {
|
|
// c is a surrogate. Make sure it is a high surrogate & that its successor is a low
|
|
// surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement character.
|
|
int low = i + 1 < endIndex ? string.charAt(i + 1) : 0;
|
|
if (c > 0xdbff || low < 0xdc00 || low > 0xdfff) {
|
|
writeByte('?');
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
// UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits)
|
|
// UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits)
|
|
// Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits)
|
|
int codePoint = 0x010000 + ((c & ~0xd800) << 10 | low & ~0xdc00);
|
|
|
|
// Emit a 21-bit character with 4 bytes.
|
|
writeByte(codePoint >> 18 | 0xf0); // 11110xxx
|
|
writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx
|
|
writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxyyyy
|
|
writeByte(codePoint & 0x3f | 0x80); // 10yyyyyy
|
|
i += 2;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
@Override public Buffer writeUtf8CodePoint(int codePoint) {
|
|
if (codePoint < 0x80) {
|
|
// Emit a 7-bit code point with 1 byte.
|
|
writeByte(codePoint);
|
|
|
|
} else if (codePoint < 0x800) {
|
|
// Emit a 11-bit code point with 2 bytes.
|
|
writeByte(codePoint >> 6 | 0xc0); // 110xxxxx
|
|
writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx
|
|
|
|
} else if (codePoint < 0x10000) {
|
|
if (codePoint >= 0xd800 && codePoint <= 0xdfff) {
|
|
throw new IllegalArgumentException(
|
|
"Unexpected code point: " + Integer.toHexString(codePoint));
|
|
}
|
|
|
|
// Emit a 16-bit code point with 3 bytes.
|
|
writeByte(codePoint >> 12 | 0xe0); // 1110xxxx
|
|
writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxxxxx
|
|
writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx
|
|
|
|
} else if (codePoint <= 0x10ffff) {
|
|
// Emit a 21-bit code point with 4 bytes.
|
|
writeByte(codePoint >> 18 | 0xf0); // 11110xxx
|
|
writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx
|
|
writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxxxxx
|
|
writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx
|
|
|
|
} else {
|
|
throw new IllegalArgumentException(
|
|
"Unexpected code point: " + Integer.toHexString(codePoint));
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
@Override public Buffer writeString(String string, Charset charset) {
|
|
return writeString(string, 0, string.length(), charset);
|
|
}
|
|
|
|
@Override
|
|
public Buffer writeString(String string, int beginIndex, int endIndex, Charset charset) {
|
|
if (string == null) throw new IllegalArgumentException("string == null");
|
|
if (beginIndex < 0) throw new IllegalAccessError("beginIndex < 0: " + beginIndex);
|
|
if (endIndex < beginIndex) {
|
|
throw new IllegalArgumentException("endIndex < beginIndex: " + endIndex + " < " + beginIndex);
|
|
}
|
|
if (endIndex > string.length()) {
|
|
throw new IllegalArgumentException(
|
|
"endIndex > string.length: " + endIndex + " > " + string.length());
|
|
}
|
|
if (charset == null) throw new IllegalArgumentException("charset == null");
|
|
if (charset.equals(Util.UTF_8)) return writeUtf8(string);
|
|
byte[] data = string.substring(beginIndex, endIndex).getBytes(charset);
|
|
return write(data, 0, data.length);
|
|
}
|
|
|
|
@Override public Buffer write(byte[] source) {
|
|
if (source == null) throw new IllegalArgumentException("source == null");
|
|
return write(source, 0, source.length);
|
|
}
|
|
|
|
@Override public Buffer write(byte[] source, int offset, int byteCount) {
|
|
if (source == null) throw new IllegalArgumentException("source == null");
|
|
checkOffsetAndCount(source.length, offset, byteCount);
|
|
|
|
int limit = offset + byteCount;
|
|
while (offset < limit) {
|
|
Segment tail = writableSegment(1);
|
|
|
|
int toCopy = Math.min(limit - offset, Segment.SIZE - tail.limit);
|
|
System.arraycopy(source, offset, tail.data, tail.limit, toCopy);
|
|
|
|
offset += toCopy;
|
|
tail.limit += toCopy;
|
|
}
|
|
|
|
size += byteCount;
|
|
return this;
|
|
}
|
|
|
|
@Override public long writeAll(Source source) throws IOException {
|
|
if (source == null) throw new IllegalArgumentException("source == null");
|
|
long totalBytesRead = 0;
|
|
for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
|
|
totalBytesRead += readCount;
|
|
}
|
|
return totalBytesRead;
|
|
}
|
|
|
|
@Override public BufferedSink write(Source source, long byteCount) throws IOException {
|
|
while (byteCount > 0) {
|
|
long read = source.read(this, byteCount);
|
|
if (read == -1) throw new EOFException();
|
|
byteCount -= read;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
@Override public Buffer writeByte(int b) {
|
|
Segment tail = writableSegment(1);
|
|
tail.data[tail.limit++] = (byte) b;
|
|
size += 1;
|
|
return this;
|
|
}
|
|
|
|
@Override public Buffer writeShort(int s) {
|
|
Segment tail = writableSegment(2);
|
|
byte[] data = tail.data;
|
|
int limit = tail.limit;
|
|
data[limit++] = (byte) ((s >>> 8) & 0xff);
|
|
data[limit++] = (byte) (s & 0xff);
|
|
tail.limit = limit;
|
|
size += 2;
|
|
return this;
|
|
}
|
|
|
|
@Override public Buffer writeShortLe(int s) {
|
|
return writeShort(Util.reverseBytesShort((short) s));
|
|
}
|
|
|
|
@Override public Buffer writeInt(int i) {
|
|
Segment tail = writableSegment(4);
|
|
byte[] data = tail.data;
|
|
int limit = tail.limit;
|
|
data[limit++] = (byte) ((i >>> 24) & 0xff);
|
|
data[limit++] = (byte) ((i >>> 16) & 0xff);
|
|
data[limit++] = (byte) ((i >>> 8) & 0xff);
|
|
data[limit++] = (byte) (i & 0xff);
|
|
tail.limit = limit;
|
|
size += 4;
|
|
return this;
|
|
}
|
|
|
|
@Override public Buffer writeIntLe(int i) {
|
|
return writeInt(Util.reverseBytesInt(i));
|
|
}
|
|
|
|
@Override public Buffer writeLong(long v) {
|
|
Segment tail = writableSegment(8);
|
|
byte[] data = tail.data;
|
|
int limit = tail.limit;
|
|
data[limit++] = (byte) ((v >>> 56L) & 0xff);
|
|
data[limit++] = (byte) ((v >>> 48L) & 0xff);
|
|
data[limit++] = (byte) ((v >>> 40L) & 0xff);
|
|
data[limit++] = (byte) ((v >>> 32L) & 0xff);
|
|
data[limit++] = (byte) ((v >>> 24L) & 0xff);
|
|
data[limit++] = (byte) ((v >>> 16L) & 0xff);
|
|
data[limit++] = (byte) ((v >>> 8L) & 0xff);
|
|
data[limit++] = (byte) (v & 0xff);
|
|
tail.limit = limit;
|
|
size += 8;
|
|
return this;
|
|
}
|
|
|
|
@Override public Buffer writeLongLe(long v) {
|
|
return writeLong(reverseBytesLong(v));
|
|
}
|
|
|
|
@Override public Buffer writeDecimalLong(long v) {
|
|
if (v == 0) {
|
|
// Both a shortcut and required since the following code can't handle zero.
|
|
return writeByte('0');
|
|
}
|
|
|
|
boolean negative = false;
|
|
if (v < 0) {
|
|
v = -v;
|
|
if (v < 0) { // Only true for Long.MIN_VALUE.
|
|
return writeUtf8("-9223372036854775808");
|
|
}
|
|
negative = true;
|
|
}
|
|
|
|
// Binary search for character width which favors matching lower numbers.
|
|
int width = //
|
|
v < 100000000L
|
|
? v < 10000L
|
|
? v < 100L
|
|
? v < 10L ? 1 : 2
|
|
: v < 1000L ? 3 : 4
|
|
: v < 1000000L
|
|
? v < 100000L ? 5 : 6
|
|
: v < 10000000L ? 7 : 8
|
|
: v < 1000000000000L
|
|
? v < 10000000000L
|
|
? v < 1000000000L ? 9 : 10
|
|
: v < 100000000000L ? 11 : 12
|
|
: v < 1000000000000000L
|
|
? v < 10000000000000L ? 13
|
|
: v < 100000000000000L ? 14 : 15
|
|
: v < 100000000000000000L
|
|
? v < 10000000000000000L ? 16 : 17
|
|
: v < 1000000000000000000L ? 18 : 19;
|
|
if (negative) {
|
|
++width;
|
|
}
|
|
|
|
Segment tail = writableSegment(width);
|
|
byte[] data = tail.data;
|
|
int pos = tail.limit + width; // We write backwards from right to left.
|
|
while (v != 0) {
|
|
int digit = (int) (v % 10);
|
|
data[--pos] = DIGITS[digit];
|
|
v /= 10;
|
|
}
|
|
if (negative) {
|
|
data[--pos] = '-';
|
|
}
|
|
|
|
tail.limit += width;
|
|
this.size += width;
|
|
return this;
|
|
}
|
|
|
|
@Override public Buffer writeHexadecimalUnsignedLong(long v) {
|
|
if (v == 0) {
|
|
// Both a shortcut and required since the following code can't handle zero.
|
|
return writeByte('0');
|
|
}
|
|
|
|
int width = Long.numberOfTrailingZeros(Long.highestOneBit(v)) / 4 + 1;
|
|
|
|
Segment tail = writableSegment(width);
|
|
byte[] data = tail.data;
|
|
for (int pos = tail.limit + width - 1, start = tail.limit; pos >= start; pos--) {
|
|
data[pos] = DIGITS[(int) (v & 0xF)];
|
|
v >>>= 4;
|
|
}
|
|
tail.limit += width;
|
|
size += width;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Returns a tail segment that we can write at least {@code minimumCapacity}
|
|
* bytes to, creating it if necessary.
|
|
*/
|
|
Segment writableSegment(int minimumCapacity) {
|
|
if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();
|
|
|
|
if (head == null) {
|
|
head = SegmentPool.take(); // Acquire a first segment.
|
|
return head.next = head.prev = head;
|
|
}
|
|
|
|
Segment tail = head.prev;
|
|
if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
|
|
tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up.
|
|
}
|
|
return tail;
|
|
}
|
|
|
|
@Override public void write(Buffer source, long byteCount) {
|
|
// Move bytes from the head of the source buffer to the tail of this buffer
|
|
// while balancing two conflicting goals: don't waste CPU and don't waste
|
|
// memory.
|
|
//
|
|
//
|
|
// Don't waste CPU (ie. don't copy data around).
|
|
//
|
|
// Copying large amounts of data is expensive. Instead, we prefer to
|
|
// reassign entire segments from one buffer to the other.
|
|
//
|
|
//
|
|
// Don't waste memory.
|
|
//
|
|
// As an invariant, adjacent pairs of segments in a buffer should be at
|
|
// least 50% full, except for the head segment and the tail segment.
|
|
//
|
|
// The head segment cannot maintain the invariant because the application is
|
|
// consuming bytes from this segment, decreasing its level.
|
|
//
|
|
// The tail segment cannot maintain the invariant because the application is
|
|
// producing bytes, which may require new nearly-empty tail segments to be
|
|
// appended.
|
|
//
|
|
//
|
|
// Moving segments between buffers
|
|
//
|
|
// When writing one buffer to another, we prefer to reassign entire segments
|
|
// over copying bytes into their most compact form. Suppose we have a buffer
|
|
// with these segment levels [91%, 61%]. If we append a buffer with a
|
|
// single [72%] segment, that yields [91%, 61%, 72%]. No bytes are copied.
|
|
//
|
|
// Or suppose we have a buffer with these segment levels: [100%, 2%], and we
|
|
// want to append it to a buffer with these segment levels [99%, 3%]. This
|
|
// operation will yield the following segments: [100%, 2%, 99%, 3%]. That
|
|
// is, we do not spend time copying bytes around to achieve more efficient
|
|
// memory use like [100%, 100%, 4%].
|
|
//
|
|
// When combining buffers, we will compact adjacent buffers when their
|
|
// combined level doesn't exceed 100%. For example, when we start with
|
|
// [100%, 40%] and append [30%, 80%], the result is [100%, 70%, 80%].
|
|
//
|
|
//
|
|
// Splitting segments
|
|
//
|
|
// Occasionally we write only part of a source buffer to a sink buffer. For
|
|
// example, given a sink [51%, 91%], we may want to write the first 30% of
|
|
// a source [92%, 82%] to it. To simplify, we first transform the source to
|
|
// an equivalent buffer [30%, 62%, 82%] and then move the head segment,
|
|
// yielding sink [51%, 91%, 30%] and source [62%, 82%].
|
|
|
|
if (source == null) throw new IllegalArgumentException("source == null");
|
|
if (source == this) throw new IllegalArgumentException("source == this");
|
|
checkOffsetAndCount(source.size, 0, byteCount);
|
|
|
|
while (byteCount > 0) {
|
|
// Is a prefix of the source's head segment all that we need to move?
|
|
if (byteCount < (source.head.limit - source.head.pos)) {
|
|
Segment tail = head != null ? head.prev : null;
|
|
if (tail != null && tail.owner
|
|
&& (byteCount + tail.limit - (tail.shared ? 0 : tail.pos) <= Segment.SIZE)) {
|
|
// Our existing segments are sufficient. Move bytes from source's head to our tail.
|
|
source.head.writeTo(tail, (int) byteCount);
|
|
source.size -= byteCount;
|
|
size += byteCount;
|
|
return;
|
|
} else {
|
|
// We're going to need another segment. Split the source's head
|
|
// segment in two, then move the first of those two to this buffer.
|
|
source.head = source.head.split((int) byteCount);
|
|
}
|
|
}
|
|
|
|
// Remove the source's head segment and append it to our tail.
|
|
Segment segmentToMove = source.head;
|
|
long movedByteCount = segmentToMove.limit - segmentToMove.pos;
|
|
source.head = segmentToMove.pop();
|
|
if (head == null) {
|
|
head = segmentToMove;
|
|
head.next = head.prev = head;
|
|
} else {
|
|
Segment tail = head.prev;
|
|
tail = tail.push(segmentToMove);
|
|
tail.compact();
|
|
}
|
|
source.size -= movedByteCount;
|
|
size += movedByteCount;
|
|
byteCount -= movedByteCount;
|
|
}
|
|
}
|
|
|
|
@Override public long read(Buffer sink, long byteCount) {
|
|
if (sink == null) throw new IllegalArgumentException("sink == null");
|
|
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
|
|
if (size == 0) return -1L;
|
|
if (byteCount > size) byteCount = size;
|
|
sink.write(this, byteCount);
|
|
return byteCount;
|
|
}
|
|
|
|
@Override public long indexOf(byte b) {
|
|
return indexOf(b, 0);
|
|
}
|
|
|
|
/**
|
|
* Returns the index of {@code b} in this at or beyond {@code fromIndex}, or
|
|
* -1 if this buffer does not contain {@code b} in that range.
|
|
*/
|
|
@Override public long indexOf(byte b, long fromIndex) {
|
|
if (fromIndex < 0) throw new IllegalArgumentException("fromIndex < 0");
|
|
|
|
Segment s = head;
|
|
if (s == null) return -1L;
|
|
long offset = 0L;
|
|
do {
|
|
int segmentByteCount = s.limit - s.pos;
|
|
if (fromIndex >= segmentByteCount) {
|
|
fromIndex -= segmentByteCount;
|
|
} else {
|
|
byte[] data = s.data;
|
|
for (int pos = (int) (s.pos + fromIndex), limit = s.limit; pos < limit; pos++) {
|
|
if (data[pos] == b) return offset + pos - s.pos;
|
|
}
|
|
fromIndex = 0;
|
|
}
|
|
offset += segmentByteCount;
|
|
s = s.next;
|
|
} while (s != head);
|
|
return -1L;
|
|
}
|
|
|
|
@Override public long indexOf(ByteString bytes) throws IOException {
|
|
return indexOf(bytes, 0);
|
|
}
|
|
|
|
@Override public long indexOf(ByteString bytes, long fromIndex) throws IOException {
|
|
if (bytes.size() == 0) throw new IllegalArgumentException("bytes is empty");
|
|
while (true) {
|
|
fromIndex = indexOf(bytes.getByte(0), fromIndex);
|
|
if (fromIndex == -1) {
|
|
return -1;
|
|
}
|
|
if (rangeEquals(fromIndex, bytes)) {
|
|
return fromIndex;
|
|
}
|
|
fromIndex++;
|
|
}
|
|
}
|
|
|
|
@Override public long indexOfElement(ByteString targetBytes) {
|
|
return indexOfElement(targetBytes, 0);
|
|
}
|
|
|
|
@Override public long indexOfElement(ByteString targetBytes, long fromIndex) {
|
|
if (fromIndex < 0) throw new IllegalArgumentException("fromIndex < 0");
|
|
|
|
Segment s = head;
|
|
if (s == null) return -1L;
|
|
long offset = 0L;
|
|
byte[] toFind = targetBytes.toByteArray();
|
|
do {
|
|
int segmentByteCount = s.limit - s.pos;
|
|
if (fromIndex >= segmentByteCount) {
|
|
fromIndex -= segmentByteCount;
|
|
} else {
|
|
byte[] data = s.data;
|
|
for (long pos = s.pos + fromIndex, limit = s.limit; pos < limit; pos++) {
|
|
byte b = data[(int) pos];
|
|
for (byte targetByte : toFind) {
|
|
if (b == targetByte) return offset + pos - s.pos;
|
|
}
|
|
}
|
|
fromIndex = 0;
|
|
}
|
|
offset += segmentByteCount;
|
|
s = s.next;
|
|
} while (s != head);
|
|
return -1L;
|
|
}
|
|
|
|
boolean rangeEquals(long offset, ByteString bytes) {
|
|
int byteCount = bytes.size();
|
|
if (size - offset < byteCount) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < byteCount; i++) {
|
|
if (getByte(offset + i) != bytes.getByte(i)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override public void flush() {
|
|
}
|
|
|
|
@Override public void close() {
|
|
}
|
|
|
|
@Override public Timeout timeout() {
|
|
return Timeout.NONE;
|
|
}
|
|
|
|
/** For testing. This returns the sizes of the segments in this buffer. */
|
|
List<Integer> segmentSizes() {
|
|
if (head == null) return Collections.emptyList();
|
|
List<Integer> result = new ArrayList<>();
|
|
result.add(head.limit - head.pos);
|
|
for (Segment s = head.next; s != head; s = s.next) {
|
|
result.add(s.limit - s.pos);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (!(o instanceof Buffer)) return false;
|
|
Buffer that = (Buffer) o;
|
|
if (size != that.size) return false;
|
|
if (size == 0) return true; // Both buffers are empty.
|
|
|
|
Segment sa = this.head;
|
|
Segment sb = that.head;
|
|
int posA = sa.pos;
|
|
int posB = sb.pos;
|
|
|
|
for (long pos = 0, count; pos < size; pos += count) {
|
|
count = Math.min(sa.limit - posA, sb.limit - posB);
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
if (sa.data[posA++] != sb.data[posB++]) return false;
|
|
}
|
|
|
|
if (posA == sa.limit) {
|
|
sa = sa.next;
|
|
posA = sa.pos;
|
|
}
|
|
|
|
if (posB == sb.limit) {
|
|
sb = sb.next;
|
|
posB = sb.pos;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override public int hashCode() {
|
|
Segment s = head;
|
|
if (s == null) return 0;
|
|
int result = 1;
|
|
do {
|
|
for (int pos = s.pos, limit = s.limit; pos < limit; pos++) {
|
|
result = 31 * result + s.data[pos];
|
|
}
|
|
s = s.next;
|
|
} while (s != head);
|
|
return result;
|
|
}
|
|
|
|
@Override public String toString() {
|
|
if (size == 0) {
|
|
return "Buffer[size=0]";
|
|
}
|
|
|
|
if (size <= 16) {
|
|
ByteString data = clone().readByteString();
|
|
return String.format("Buffer[size=%s data=%s]", size, data.hex());
|
|
}
|
|
|
|
try {
|
|
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
|
md5.update(head.data, head.pos, head.limit - head.pos);
|
|
for (Segment s = head.next; s != head; s = s.next) {
|
|
md5.update(s.data, s.pos, s.limit - s.pos);
|
|
}
|
|
return String.format("Buffer[size=%s md5=%s]",
|
|
size, ByteString.of(md5.digest()).hex());
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new AssertionError();
|
|
}
|
|
}
|
|
|
|
/** Returns a deep copy of this buffer. */
|
|
@Override public Buffer clone() {
|
|
Buffer result = new Buffer();
|
|
if (size == 0) return result;
|
|
|
|
result.head = new Segment(head);
|
|
result.head.next = result.head.prev = result.head;
|
|
for (Segment s = head.next; s != head; s = s.next) {
|
|
result.head.prev.push(new Segment(s));
|
|
}
|
|
result.size = size;
|
|
return result;
|
|
}
|
|
|
|
/** Returns an immutable copy of this buffer as a byte string. */
|
|
public ByteString snapshot() {
|
|
if (size > Integer.MAX_VALUE) {
|
|
throw new IllegalArgumentException("size > Integer.MAX_VALUE: " + size);
|
|
}
|
|
return snapshot((int) size);
|
|
}
|
|
|
|
/**
|
|
* Returns an immutable copy of the first {@code byteCount} bytes of this buffer as a byte string.
|
|
*/
|
|
public ByteString snapshot(int byteCount) {
|
|
if (byteCount == 0) return ByteString.EMPTY;
|
|
return new SegmentedByteString(this, byteCount);
|
|
}
|
|
}
|