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 */
16package 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 */
27final 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