Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright (C) 2013 Square, Inc. |
michael@0 | 3 | * |
michael@0 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 5 | * you may not use this file except in compliance with the License. |
michael@0 | 6 | * You may obtain a copy of the License at |
michael@0 | 7 | * |
michael@0 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 9 | * |
michael@0 | 10 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 13 | * See the License for the specific language governing permissions and |
michael@0 | 14 | * limitations under the License. |
michael@0 | 15 | */ |
michael@0 | 16 | package com.squareup.picasso; |
michael@0 | 17 | |
michael@0 | 18 | import java.io.BufferedInputStream; |
michael@0 | 19 | import java.io.IOException; |
michael@0 | 20 | import java.io.InputStream; |
michael@0 | 21 | |
michael@0 | 22 | /** |
michael@0 | 23 | * An input stream wrapper that supports unlimited independent cursors for |
michael@0 | 24 | * marking and resetting. Each cursor is a token, and it's the caller's |
michael@0 | 25 | * responsibility to keep track of these. |
michael@0 | 26 | */ |
michael@0 | 27 | final class MarkableInputStream extends InputStream { |
michael@0 | 28 | private final InputStream in; |
michael@0 | 29 | |
michael@0 | 30 | private long offset; |
michael@0 | 31 | private long reset; |
michael@0 | 32 | private long limit; |
michael@0 | 33 | |
michael@0 | 34 | private long defaultMark = -1; |
michael@0 | 35 | |
michael@0 | 36 | public MarkableInputStream(InputStream in) { |
michael@0 | 37 | if (!in.markSupported()) { |
michael@0 | 38 | in = new BufferedInputStream(in); |
michael@0 | 39 | } |
michael@0 | 40 | this.in = in; |
michael@0 | 41 | } |
michael@0 | 42 | |
michael@0 | 43 | /** Marks this place in the stream so we can reset back to it later. */ |
michael@0 | 44 | @Override public void mark(int readLimit) { |
michael@0 | 45 | defaultMark = savePosition(readLimit); |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | /** |
michael@0 | 49 | * Returns an opaque token representing the current position in the stream. |
michael@0 | 50 | * Call {@link #reset(long)} to return to this position in the stream later. |
michael@0 | 51 | * It is an error to call {@link #reset(long)} after consuming more than |
michael@0 | 52 | * {@code readLimit} bytes from this stream. |
michael@0 | 53 | */ |
michael@0 | 54 | public long savePosition(int readLimit) { |
michael@0 | 55 | long offsetLimit = offset + readLimit; |
michael@0 | 56 | if (limit < offsetLimit) { |
michael@0 | 57 | setLimit(offsetLimit); |
michael@0 | 58 | } |
michael@0 | 59 | return offset; |
michael@0 | 60 | } |
michael@0 | 61 | |
michael@0 | 62 | /** |
michael@0 | 63 | * Makes sure that the underlying stream can backtrack the full range from |
michael@0 | 64 | * {@code reset} thru {@code limit}. Since we can't call {@code mark()} |
michael@0 | 65 | * without also adjusting the reset-to-position on the underlying stream this |
michael@0 | 66 | * method resets first and then marks the union of the two byte ranges. On |
michael@0 | 67 | * buffered streams this additional cursor motion shouldn't result in any |
michael@0 | 68 | * additional I/O. |
michael@0 | 69 | */ |
michael@0 | 70 | private void setLimit(long limit) { |
michael@0 | 71 | try { |
michael@0 | 72 | if (reset < offset && offset <= this.limit) { |
michael@0 | 73 | in.reset(); |
michael@0 | 74 | in.mark((int) (limit - reset)); |
michael@0 | 75 | skip(reset, offset); |
michael@0 | 76 | } else { |
michael@0 | 77 | reset = offset; |
michael@0 | 78 | in.mark((int) (limit - offset)); |
michael@0 | 79 | } |
michael@0 | 80 | this.limit = limit; |
michael@0 | 81 | } catch (IOException e) { |
michael@0 | 82 | throw new IllegalStateException("Unable to mark: " + e); |
michael@0 | 83 | } |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | /** Resets the stream to the most recent {@link #mark mark}. */ |
michael@0 | 87 | @Override public void reset() throws IOException { |
michael@0 | 88 | reset(defaultMark); |
michael@0 | 89 | } |
michael@0 | 90 | |
michael@0 | 91 | /** Resets the stream to the position recorded by {@code token}. */ |
michael@0 | 92 | public void reset(long token) throws IOException { |
michael@0 | 93 | if (offset > limit || token < reset) { |
michael@0 | 94 | throw new IOException("Cannot reset"); |
michael@0 | 95 | } |
michael@0 | 96 | in.reset(); |
michael@0 | 97 | skip(reset, token); |
michael@0 | 98 | offset = token; |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | /** Skips {@code target - current} bytes and returns. */ |
michael@0 | 102 | private void skip(long current, long target) throws IOException { |
michael@0 | 103 | while (current < target) { |
michael@0 | 104 | long skipped = in.skip(target - current); |
michael@0 | 105 | if (skipped == 0) { |
michael@0 | 106 | if (read() == -1) { |
michael@0 | 107 | break; // EOF |
michael@0 | 108 | } else { |
michael@0 | 109 | skipped = 1; |
michael@0 | 110 | } |
michael@0 | 111 | } |
michael@0 | 112 | current += skipped; |
michael@0 | 113 | } |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | @Override public int read() throws IOException { |
michael@0 | 117 | int result = in.read(); |
michael@0 | 118 | if (result != -1) { |
michael@0 | 119 | offset++; |
michael@0 | 120 | } |
michael@0 | 121 | return result; |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | @Override public int read(byte[] buffer) throws IOException { |
michael@0 | 125 | int count = in.read(buffer); |
michael@0 | 126 | if (count != -1) { |
michael@0 | 127 | offset += count; |
michael@0 | 128 | } |
michael@0 | 129 | return count; |
michael@0 | 130 | } |
michael@0 | 131 | |
michael@0 | 132 | @Override public int read(byte[] buffer, int offset, int length) throws IOException { |
michael@0 | 133 | int count = in.read(buffer, offset, length); |
michael@0 | 134 | if (count != -1) { |
michael@0 | 135 | this.offset += count; |
michael@0 | 136 | } |
michael@0 | 137 | return count; |
michael@0 | 138 | } |
michael@0 | 139 | |
michael@0 | 140 | @Override public long skip(long byteCount) throws IOException { |
michael@0 | 141 | long skipped = in.skip(byteCount); |
michael@0 | 142 | offset += skipped; |
michael@0 | 143 | return skipped; |
michael@0 | 144 | } |
michael@0 | 145 | |
michael@0 | 146 | @Override public int available() throws IOException { |
michael@0 | 147 | return in.available(); |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | @Override public void close() throws IOException { |
michael@0 | 151 | in.close(); |
michael@0 | 152 | } |
michael@0 | 153 | |
michael@0 | 154 | @Override public boolean markSupported() { |
michael@0 | 155 | return in.markSupported(); |
michael@0 | 156 | } |
michael@0 | 157 | } |