/*
* 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 okio;
import javax.annotation.
Nullable;
/**
* A segment of a buffer.
*
* <p>Each segment in a buffer is a circularly-linked list node referencing the following and
* preceding segments in the buffer.
*
* <p>Each segment in the pool is a singly-linked list node referencing the rest of segments in the
* pool.
*
* <p>The underlying byte arrays of segments may be shared between buffers and byte strings. When a
* segment's byte array is shared the segment may not be recycled, nor may its byte data be changed.
* The lone exception is that the owner segment is allowed to append to the segment, writing data at
* {@code limit} and beyond. There is a single owning segment for each byte array. Positions,
* limits, prev, and next references are not shared.
*/
final class
Segment {
/** The size of all segments in bytes. */
static final int
SIZE = 8192;
/** Segments will be shared when doing so avoids {@code arraycopy()} of this many bytes. */
static final int
SHARE_MINIMUM = 1024;
final byte[]
data;
/** The next byte of application data byte to read in this segment. */
int
pos;
/** The first byte of available data ready to be written to. */
int
limit;
/** True if other segments or byte strings use the same byte array. */
boolean
shared;
/** True if this segment owns the byte array and can append to it, extending {@code limit}. */
boolean
owner;
/** Next segment in a linked or circularly-linked list. */
Segment next;
/** Previous segment in a circularly-linked list. */
Segment prev;
Segment() {
this.
data = new byte[
SIZE];
this.
owner = true;
this.
shared = false;
}
Segment(byte[]
data, int
pos, int
limit, boolean
shared, boolean
owner) {
this.
data =
data;
this.
pos =
pos;
this.
limit =
limit;
this.
shared =
shared;
this.
owner =
owner;
}
/**
* Returns a new segment that shares the underlying byte array with this. Adjusting pos and limit
* are safe but writes are forbidden. This also marks the current segment as shared, which
* prevents it from being pooled.
*/
final
Segment sharedCopy() {
shared = true;
return new
Segment(
data,
pos,
limit, true, false);
}
/** Returns a new segment that its own private copy of the underlying byte array. */
final
Segment unsharedCopy() {
return new
Segment(
data.
clone(),
pos,
limit, false, true);
}
/**
* Removes this segment of a circularly-linked list and returns its successor.
* Returns null if the list is now empty.
*/
public final @
Nullable Segment pop() {
Segment result =
next != this ?
next : null;
prev.
next =
next;
next.
prev =
prev;
next = null;
prev = null;
return
result;
}
/**
* Appends {@code segment} after this segment in the circularly-linked list.
* Returns the pushed segment.
*/
public final
Segment push(
Segment segment) {
segment.
prev = this;
segment.
next =
next;
next.
prev =
segment;
next =
segment;
return
segment;
}
/**
* Splits this head of a circularly-linked list into two segments. The first
* segment contains the data in {@code [pos..pos+byteCount)}. The second
* segment contains the data in {@code [pos+byteCount..limit)}. This can be
* useful when moving partial segments from one buffer to another.
*
* <p>Returns the new head of the circularly-linked list.
*/
public final
Segment split(int
byteCount) {
if (
byteCount <= 0 ||
byteCount >
limit -
pos) throw new
IllegalArgumentException();
Segment prefix;
// We have two competing performance goals:
// - Avoid copying data. We accomplish this by sharing segments.
// - Avoid short shared segments. These are bad for performance because they are readonly and
// may lead to long chains of short segments.
// To balance these goals we only share segments when the copy will be large.
if (
byteCount >=
SHARE_MINIMUM) {
prefix =
sharedCopy();
} else {
prefix =
SegmentPool.
take();
System.
arraycopy(
data,
pos,
prefix.
data, 0,
byteCount);
}
prefix.
limit =
prefix.
pos +
byteCount;
pos +=
byteCount;
prev.
push(
prefix);
return
prefix;
}
/**
* Call this when the tail and its predecessor may both be less than half
* full. This will copy data so that segments can be recycled.
*/
public final void
compact() {
if (
prev == this) throw new
IllegalStateException();
if (!
prev.
owner) return; // Cannot compact: prev isn't writable.
int
byteCount =
limit -
pos;
int
availableByteCount =
SIZE -
prev.
limit + (
prev.
shared ? 0 :
prev.
pos);
if (
byteCount >
availableByteCount) return; // Cannot compact: not enough writable space.
writeTo(
prev,
byteCount);
pop();
SegmentPool.
recycle(this);
}
/** Moves {@code byteCount} bytes from this segment to {@code sink}. */
public final void
writeTo(
Segment sink, int
byteCount) {
if (!
sink.
owner) throw new
IllegalArgumentException();
if (
sink.
limit +
byteCount >
SIZE) {
// We can't fit byteCount bytes at the sink's current position. Shift sink first.
if (
sink.
shared) throw new
IllegalArgumentException();
if (
sink.
limit +
byteCount -
sink.
pos >
SIZE) throw new
IllegalArgumentException();
System.
arraycopy(
sink.
data,
sink.
pos,
sink.
data, 0,
sink.
limit -
sink.
pos);
sink.
limit -=
sink.
pos;
sink.
pos = 0;
}
System.
arraycopy(
data,
pos,
sink.
data,
sink.
limit,
byteCount);
sink.
limit +=
byteCount;
pos +=
byteCount;
}
}