summaryrefslogtreecommitdiffstats
path: root/mobile/android/thirdparty/com/squareup/picasso/MarkableInputStream.java
blob: 17043a1b037a7bc87c8aa62a286d952c2f0359c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
 * Copyright (C) 2013 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 com.squareup.picasso;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * An input stream wrapper that supports unlimited independent cursors for
 * marking and resetting. Each cursor is a token, and it's the caller's
 * responsibility to keep track of these.
 */
final class MarkableInputStream extends InputStream {
  private final InputStream in;

  private long offset;
  private long reset;
  private long limit;

  private long defaultMark = -1;

  public MarkableInputStream(InputStream in) {
    if (!in.markSupported()) {
      in = new BufferedInputStream(in);
    }
    this.in = in;
  }

  /** Marks this place in the stream so we can reset back to it later. */
  @Override public void mark(int readLimit) {
    defaultMark = savePosition(readLimit);
  }

  /**
   * Returns an opaque token representing the current position in the stream.
   * Call {@link #reset(long)} to return to this position in the stream later.
   * It is an error to call {@link #reset(long)} after consuming more than
   * {@code readLimit} bytes from this stream.
   */
  public long savePosition(int readLimit) {
    long offsetLimit = offset + readLimit;
    if (limit < offsetLimit) {
      setLimit(offsetLimit);
    }
    return offset;
  }

  /**
   * Makes sure that the underlying stream can backtrack the full range from
   * {@code reset} thru {@code limit}. Since we can't call {@code mark()}
   * without also adjusting the reset-to-position on the underlying stream this
   * method resets first and then marks the union of the two byte ranges. On
   * buffered streams this additional cursor motion shouldn't result in any
   * additional I/O.
   */
  private void setLimit(long limit) {
    try {
      if (reset < offset && offset <= this.limit) {
        in.reset();
        in.mark((int) (limit - reset));
        skip(reset, offset);
      } else {
        reset = offset;
        in.mark((int) (limit - offset));
      }
      this.limit = limit;
    } catch (IOException e) {
      throw new IllegalStateException("Unable to mark: " + e);
    }
  }

  /** Resets the stream to the most recent {@link #mark mark}. */
  @Override public void reset() throws IOException {
    reset(defaultMark);
  }

  /** Resets the stream to the position recorded by {@code token}. */
  public void reset(long token) throws IOException {
    if (offset > limit || token < reset) {
      throw new IOException("Cannot reset");
    }
    in.reset();
    skip(reset, token);
    offset = token;
  }

  /** Skips {@code target - current} bytes and returns. */
  private void skip(long current, long target) throws IOException {
    while (current < target) {
      long skipped = in.skip(target - current);
      if (skipped == 0) {
        if (read() == -1) {
          break; // EOF
        } else {
          skipped = 1;
        }
      }
      current += skipped;
    }
  }

  @Override public int read() throws IOException {
    int result = in.read();
    if (result != -1) {
      offset++;
    }
    return result;
  }

  @Override public int read(byte[] buffer) throws IOException {
    int count = in.read(buffer);
    if (count != -1) {
      offset += count;
    }
    return count;
  }

  @Override public int read(byte[] buffer, int offset, int length) throws IOException {
    int count = in.read(buffer, offset, length);
    if (count != -1) {
      this.offset += count;
    }
    return count;
  }

  @Override public long skip(long byteCount) throws IOException {
    long skipped = in.skip(byteCount);
    offset += skipped;
    return skipped;
  }

  @Override public int available() throws IOException {
    return in.available();
  }

  @Override public void close() throws IOException {
    in.close();
  }

  @Override public boolean markSupported() {
    return in.markSupported();
  }
}