1 /* 2 * Copyright (C) 2014 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package okio; 17 18 /** 19 * A segment of an OkBuffer. 20 * 21 * <p>Each segment in an OkBuffer is a circularly-linked list node referencing 22 * the following and preceding segments in the buffer. 23 * 24 * <p>Each segment in the pool is a singly-linked list node referencing the rest 25 * of segments in the pool. 26 */ 27 final class Segment { 28 /** The size of all segments in bytes. */ 29 // TODO: Using fixed-size segments makes pooling easier. But it harms memory 30 // efficiency and encourages copying. Try variable sized segments? 31 // TODO: Is 2 KiB a good default segment size? 32 static final int SIZE = 2048; 33 34 final byte[] data = new byte[SIZE]; 35 36 /** The next byte of application data byte to read in this segment. */ 37 int pos; 38 39 /** The first byte of available data ready to be written to. */ 40 int limit; 41 42 /** Next segment in a linked or circularly-linked list. */ 43 Segment next; 44 45 /** Previous segment in a circularly-linked list. */ 46 Segment prev; 47 48 /** 49 * Removes this segment of a circularly-linked list and returns its successor. 50 * Returns null if the list is now empty. 51 */ 52 public Segment pop() { 53 Segment result = next != this ? next : null; 54 prev.next = next; 55 next.prev = prev; 56 next = null; 57 prev = null; 58 return result; 59 } 60 61 /** 62 * Appends {@code segment} after this segment in the circularly-linked list. 63 * Returns the pushed segment. 64 */ 65 public Segment push(Segment segment) { 66 segment.prev = this; 67 segment.next = next; 68 next.prev = segment; 69 next = segment; 70 return segment; 71 } 72 73 /** 74 * Splits this head of a circularly-linked list into two segments. The first 75 * segment contains the data in {@code [pos..pos+byteCount)}. The second 76 * segment contains the data in {@code [pos+byteCount..limit)}. This can be 77 * useful when moving partial segments from one OkBuffer to another. 78 * 79 * <p>Returns the new head of the circularly-linked list. 80 */ 81 public Segment split(int byteCount) { 82 int aSize = byteCount; 83 int bSize = (limit - pos) - byteCount; 84 if (aSize <= 0 || bSize <= 0) throw new IllegalArgumentException(); 85 86 // Which side of the split is larger? We want to copy as few bytes as possible. 87 if (aSize < bSize) { 88 // Create a segment of size 'aSize' before this segment. 89 Segment before = SegmentPool.INSTANCE.take(); 90 System.arraycopy(data, pos, before.data, before.pos, aSize); 91 pos += aSize; 92 before.limit += aSize; 93 prev.push(before); 94 return before; 95 } else { 96 // Create a new segment of size 'bSize' after this segment. 97 Segment after = SegmentPool.INSTANCE.take(); 98 System.arraycopy(data, pos + aSize, after.data, after.pos, bSize); 99 limit -= bSize; 100 after.limit += bSize; 101 push(after); 102 return this; 103 } 104 } 105 106 /** 107 * Call this when the tail and its predecessor may both be less than half 108 * full. This will copy data so that segments can be recycled. 109 */ 110 public void compact() { 111 if (prev == this) throw new IllegalStateException(); 112 if ((prev.limit - prev.pos) + (limit - pos) > SIZE) return; // Cannot compact. 113 writeTo(prev, limit - pos); 114 pop(); 115 SegmentPool.INSTANCE.recycle(this); 116 } 117 118 /** Moves {@code byteCount} bytes from {@code sink} to this segment. */ 119 // TODO: if sink has fewer bytes than this, it may be cheaper to reverse the 120 // direction of the copy and swap the segments! 121 public void writeTo(Segment sink, int byteCount) { 122 if (byteCount + (sink.limit - sink.pos) > SIZE) throw new IllegalArgumentException(); 123 124 if (sink.limit + byteCount > SIZE) { 125 // We can't fit byteCount bytes at the sink's current position. Compact sink first. 126 System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos); 127 sink.limit -= sink.pos; 128 sink.pos = 0; 129 } 130 131 System.arraycopy(data, pos, sink.data, sink.limit, byteCount); 132 sink.limit += byteCount; 133 pos += byteCount; 134 } 135 } 136