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 a buffer.
20 *
21 * <p>Each segment in a buffer is a circularly-linked list node referencing the following and
22 * preceding segments in the buffer.
23 *
24 * <p>Each segment in the pool is a singly-linked list node referencing the rest of segments in the
25 * pool.
26 *
27 * <p>The underlying byte arrays of segments may be shared between buffers and byte strings. When a
28 * segment's byte array is shared the segment may not be recycled, nor may its byte data be changed.
29 * The lone exception is that the owner segment is allowed to append to the segment, writing data at
30 * {@code limit} and beyond. There is a single owning segment for each byte array. Positions,
31 * limits, prev, and next references are not shared.
32 */
33final class Segment {
34  /** The size of all segments in bytes. */
35  static final int SIZE = 8192;
36
37  final byte[] data;
38
39  /** The next byte of application data byte to read in this segment. */
40  int pos;
41
42  /** The first byte of available data ready to be written to. */
43  int limit;
44
45  /** True if other segments or byte strings use the same byte array. */
46  boolean shared;
47
48  /** True if this segment owns the byte array and can append to it, extending {@code limit}. */
49  boolean owner;
50
51  /** Next segment in a linked or circularly-linked list. */
52  Segment next;
53
54  /** Previous segment in a circularly-linked list. */
55  Segment prev;
56
57  Segment() {
58    this.data = new byte[SIZE];
59    this.owner = true;
60    this.shared = false;
61  }
62
63  Segment(Segment shareFrom) {
64    this(shareFrom.data, shareFrom.pos, shareFrom.limit);
65    shareFrom.shared = true;
66  }
67
68  Segment(byte[] data, int pos, int limit) {
69    this.data = data;
70    this.pos = pos;
71    this.limit = limit;
72    this.owner = false;
73    this.shared = true;
74  }
75
76  /**
77   * Removes this segment of a circularly-linked list and returns its successor.
78   * Returns null if the list is now empty.
79   */
80  public Segment pop() {
81    Segment result = next != this ? next : null;
82    prev.next = next;
83    next.prev = prev;
84    next = null;
85    prev = null;
86    return result;
87  }
88
89  /**
90   * Appends {@code segment} after this segment in the circularly-linked list.
91   * Returns the pushed segment.
92   */
93  public Segment push(Segment segment) {
94    segment.prev = this;
95    segment.next = next;
96    next.prev = segment;
97    next = segment;
98    return segment;
99  }
100
101  /**
102   * Splits this head of a circularly-linked list into two segments. The first
103   * segment contains the data in {@code [pos..pos+byteCount)}. The second
104   * segment contains the data in {@code [pos+byteCount..limit)}. This can be
105   * useful when moving partial segments from one buffer to another.
106   *
107   * <p>Returns the new head of the circularly-linked list.
108   */
109  public Segment split(int byteCount) {
110    if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
111    Segment prefix = new Segment(this);
112    prefix.limit = prefix.pos + byteCount;
113    pos += byteCount;
114    prev.push(prefix);
115    return prefix;
116  }
117
118  /**
119   * Call this when the tail and its predecessor may both be less than half
120   * full. This will copy data so that segments can be recycled.
121   */
122  public void compact() {
123    if (prev == this) throw new IllegalStateException();
124    if (!prev.owner) return; // Cannot compact: prev isn't writable.
125    int byteCount = limit - pos;
126    int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
127    if (byteCount > availableByteCount) return; // Cannot compact: not enough writable space.
128    writeTo(prev, byteCount);
129    pop();
130    SegmentPool.recycle(this);
131  }
132
133  /** Moves {@code byteCount} bytes from this segment to {@code sink}. */
134  public void writeTo(Segment sink, int byteCount) {
135    if (!sink.owner) throw new IllegalArgumentException();
136    if (sink.limit + byteCount > SIZE) {
137      // We can't fit byteCount bytes at the sink's current position. Shift sink first.
138      if (sink.shared) throw new IllegalArgumentException();
139      if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
140      System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
141      sink.limit -= sink.pos;
142      sink.pos = 0;
143    }
144
145    System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
146    sink.limit += byteCount;
147    pos += byteCount;
148  }
149}
150