Home | History | Annotate | Download | only in okio
      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