forked from aws/aws-sdk-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRepeatableInputStream.java
More file actions
199 lines (177 loc) · 6.8 KB
/
RepeatableInputStream.java
File metadata and controls
199 lines (177 loc) · 6.8 KB
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Portions copyright 2006-2009 James Murty. Please see LICENSE.txt
* for applicable license terms and NOTICE.txt for applicable notices.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.amazonaws.services.s3.internal;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A repeatable input stream wrapper for any input stream. This input stream
* relies on buffered data to repeat, and can therefore only be repeated when
* less data has been read than this buffer can hold.
* <p>
* <b>Note:</b> Always use a {@link RepeatableFileInputStream} instead of this
* class if you are sourcing data from a file, as the file-based repeatable
* input stream can be repeated without any limitations.
*/
public class RepeatableInputStream extends InputStream {
private static final Log log = LogFactory.getLog(RepeatableInputStream.class);
private InputStream is = null;
private int bufferSize = 0;
private int bufferOffset = 0;
private long bytesReadPastMark = 0;
private byte[] buffer = null;
/**
* Creates a repeatable input stream based on another input stream.
*
* @param inputStream
* The input stream to wrap. The data read from the wrapped input
* stream is buffered as it is read, up to the buffer limit
* specified.
* @param bufferSize
* The number of bytes buffered by this class.
*/
public RepeatableInputStream(InputStream inputStream, int bufferSize) {
if (inputStream == null) {
throw new IllegalArgumentException("InputStream cannot be null");
}
this.is = inputStream;
this.bufferSize = bufferSize;
this.buffer = new byte[this.bufferSize];
if (log.isDebugEnabled()) {
log.debug("Underlying input stream will be repeatable up to "
+ this.buffer.length + " bytes");
}
}
/**
* Resets the input stream to the beginning by pointing the buffer offset to
* the beginning of the available data buffer.
*
* @throws IOException
* When the available buffer size has been exceeded, in which
* case the input stream data cannot be repeated.
*/
public void reset() throws IOException {
if (bytesReadPastMark <= bufferSize) {
if (log.isDebugEnabled()) {
log.debug("Reset after reading " + bytesReadPastMark + " bytes.");
}
bufferOffset = 0;
} else {
throw new IOException(
"Input stream cannot be reset as " + this.bytesReadPastMark
+ " bytes have been written, exceeding the available buffer size of " + this.bufferSize);
}
}
/**
* @see java.io.InputStream#markSupported()
*/
public boolean markSupported() {
return true;
}
/**
* This method can only be used while less data has been read from the input
* stream than fits into the buffer. The readLimit parameter is ignored
* entirely.
*/
public synchronized void mark(int readlimit) {
if (log.isDebugEnabled()) {
log.debug("Input stream marked at " + bytesReadPastMark + " bytes");
}
if (bytesReadPastMark <= bufferSize && buffer != null) {
/*
* Clear buffer of already-read data to make more space. It's safe
* to cast bytesReadPastMark to an int because it is known to be
* less than bufferSize, which is an int.
*/
byte[] newBuffer = new byte[this.bufferSize];
System.arraycopy(buffer, bufferOffset, newBuffer, 0, (int)(bytesReadPastMark - bufferOffset));
this.buffer = newBuffer;
this.bytesReadPastMark -= bufferOffset;
this.bufferOffset = 0;
} else {
// If mark is called after the buffer was already exceeded, create a new buffer.
this.bufferOffset = 0;
this.bytesReadPastMark = 0;
this.buffer = new byte[this.bufferSize];
}
}
/**
* @see java.io.InputStream#available()
*/
public int available() throws IOException {
return is.available();
}
/**
* @see java.io.InputStream#close()
*/
public void close() throws IOException {
is.close();
}
/**
* @see java.io.InputStream#read(byte[], int, int)
*/
public int read(byte[] out, int outOffset, int outLength) throws IOException {
// Check whether we already have buffered data.
if (bufferOffset < bytesReadPastMark && buffer != null) {
// Data is being repeated, so read from buffer instead of wrapped input stream.
int bytesFromBuffer = outLength;
if (bufferOffset + bytesFromBuffer > bytesReadPastMark) {
bytesFromBuffer = (int) bytesReadPastMark - bufferOffset;
}
// Write to output.
System.arraycopy(buffer, bufferOffset, out, outOffset, bytesFromBuffer);
bufferOffset += bytesFromBuffer;
return bytesFromBuffer;
}
// Read data from input stream.
int count = is.read(out, outOffset, outLength);
if (count <= 0) {
return count;
}
// Fill the buffer with data, as long as we won't exceed its capacity.
if (bytesReadPastMark + count <= bufferSize) {
System.arraycopy(out, outOffset, buffer, (int) bytesReadPastMark, count);
bufferOffset += count;
} else {
// We have exceeded the buffer capacity, after which point it is of no use. Free the memory.
if (log.isDebugEnabled()) {
log.debug("Buffer size " + bufferSize + " has been exceeded and the input stream "
+ "will not be repeatable until the next mark. Freeing buffer memory");
}
buffer = null;
}
bytesReadPastMark += count;
return count;
}
/**
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
byte[] tmp = new byte[1];
int count = read(tmp);
if (count != -1) {
return tmp[0];
} else {
return count;
}
}
public InputStream getWrappedInputStream() {
return is;
}
}