| /* |
| * 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 kotlin.jvm.JvmField |
| |
| /** |
| * A segment of a buffer. |
| * |
| * Each segment in a buffer is a circularly-linked list node referencing the following and |
| * preceding segments in the buffer. |
| * |
| * Each segment in the pool is a singly-linked list node referencing the rest of segments in the |
| * pool. |
| * |
| * 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 |
| * `limit` and beyond. There is a single owning segment for each byte array. Positions, |
| * limits, prev, and next references are not shared. |
| */ |
| internal class Segment { |
| @JvmField val data: ByteArray |
| |
| /** The next byte of application data byte to read in this segment. */ |
| @JvmField var pos: Int = 0 |
| |
| /** |
| * The first byte of available data ready to be written to. |
| * |
| * If the segment is free and linked in the segment pool, the field contains total |
| * byte count of this and next segments. |
| */ |
| @JvmField var limit: Int = 0 |
| |
| /** True if other segments or byte strings use the same byte array. */ |
| @JvmField var shared: Boolean = false |
| |
| /** True if this segment owns the byte array and can append to it, extending `limit`. */ |
| @JvmField var owner: Boolean = false |
| |
| /** Next segment in a linked or circularly-linked list. */ |
| @JvmField var next: Segment? = null |
| |
| /** Previous segment in a circularly-linked list. */ |
| @JvmField var prev: Segment? = null |
| |
| constructor() { |
| this.data = ByteArray(SIZE) |
| this.owner = true |
| this.shared = false |
| } |
| |
| constructor(data: ByteArray, pos: Int, limit: Int, shared: Boolean, owner: Boolean) { |
| 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. |
| */ |
| fun sharedCopy(): Segment { |
| shared = true |
| return Segment(data, pos, limit, true, false) |
| } |
| |
| /** Returns a new segment that its own private copy of the underlying byte array. */ |
| fun unsharedCopy() = Segment(data.copyOf(), pos, limit, false, true) |
| |
| /** |
| * Removes this segment of a circularly-linked list and returns its successor. |
| * Returns null if the list is now empty. |
| */ |
| fun pop(): Segment? { |
| val result = if (next !== this) next else null |
| prev!!.next = next |
| next!!.prev = prev |
| next = null |
| prev = null |
| return result |
| } |
| |
| /** |
| * Appends `segment` after this segment in the circularly-linked list. Returns the pushed segment. |
| */ |
| fun push(segment: 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 `[pos..pos+byteCount)`. The second segment contains the data in |
| * `[pos+byteCount..limit)`. This can be useful when moving partial segments from one buffer to |
| * another. |
| * |
| * Returns the new head of the circularly-linked list. |
| */ |
| fun split(byteCount: Int): Segment { |
| require(byteCount > 0 && byteCount <= limit - pos) { "byteCount out of range" } |
| val prefix: Segment |
| |
| // 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() |
| data.copyInto(prefix.data, startIndex = pos, endIndex = pos + 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. |
| */ |
| fun compact() { |
| check(prev !== this) { "cannot compact" } |
| if (!prev!!.owner) return // Cannot compact: prev isn't writable. |
| val byteCount = limit - pos |
| val availableByteCount = SIZE - prev!!.limit + if (prev!!.shared) 0 else prev!!.pos |
| if (byteCount > availableByteCount) return // Cannot compact: not enough writable space. |
| writeTo(prev!!, byteCount) |
| pop() |
| SegmentPool.recycle(this) |
| } |
| |
| /** Moves `byteCount` bytes from this segment to `sink`. */ |
| fun writeTo(sink: Segment, byteCount: Int) { |
| check(sink.owner) { "only owner can write" } |
| if (sink.limit + byteCount > SIZE) { |
| // We can't fit byteCount bytes at the sink's current position. Shift sink first. |
| if (sink.shared) throw IllegalArgumentException() |
| if (sink.limit + byteCount - sink.pos > SIZE) throw IllegalArgumentException() |
| sink.data.copyInto(sink.data, startIndex = sink.pos, endIndex = sink.limit) |
| sink.limit -= sink.pos |
| sink.pos = 0 |
| } |
| |
| data.copyInto( |
| sink.data, destinationOffset = sink.limit, startIndex = pos, |
| endIndex = pos + byteCount |
| ) |
| sink.limit += byteCount |
| pos += byteCount |
| } |
| |
| companion object { |
| /** The size of all segments in bytes. */ |
| const val SIZE = 8192 |
| |
| /** Segments will be shared when doing so avoids `arraycopy()` of this many bytes. */ |
| const val SHARE_MINIMUM = 1024 |
| } |
| } |