View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.common.bytesource;
18  
19  import java.io.BufferedInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.Objects;
24  
25  import org.apache.commons.imaging.common.BinaryFunctions;
26  
27  public class ByteSourceInputStream extends ByteSource {
28      private static final int BLOCK_SIZE = 1024;
29  
30      private final InputStream is;
31      private CacheBlock cacheHead;
32      private byte[] readBuffer;
33      private long streamLength = -1;
34  
35      public ByteSourceInputStream(final InputStream is, final String fileName) {
36          super(fileName);
37          this.is = new BufferedInputStream(is);
38      }
39  
40      private class CacheBlock {
41          public final byte[] bytes;
42          private CacheBlock next;
43          private boolean triedNext;
44  
45          CacheBlock(final byte[] bytes) {
46              this.bytes = bytes;
47          }
48  
49          public CacheBlock getNext() throws IOException {
50              if (null != next) {
51                  return next;
52              }
53              if (triedNext) {
54                  return null;
55              }
56              triedNext = true;
57              next = readBlock();
58              return next;
59          }
60  
61      }
62  
63      private CacheBlock readBlock() throws IOException {
64          if (null == readBuffer) {
65              readBuffer = new byte[BLOCK_SIZE];
66          }
67  
68          final int read = is.read(readBuffer);
69          if (read < 1) {
70              return null;
71          }
72          if (read < BLOCK_SIZE) {
73              // return a copy.
74              final byte[] result = new byte[read];
75              System.arraycopy(readBuffer, 0, result, 0, read);
76              return new CacheBlock(result);
77          }
78          // return current buffer.
79          final byte[] result = readBuffer;
80          readBuffer = null;
81          return new CacheBlock(result);
82      }
83  
84      private CacheBlock getFirstBlock() throws IOException {
85          if (null == cacheHead) {
86              cacheHead = readBlock();
87          }
88          return cacheHead;
89      }
90  
91      private class CacheReadingInputStream extends InputStream {
92          private CacheBlock block;
93          private boolean readFirst;
94          private int blockIndex;
95  
96          @Override
97          public int read() throws IOException {
98              if (null == block) {
99                  if (readFirst) {
100                     return -1;
101                 }
102                 block = getFirstBlock();
103                 readFirst = true;
104             }
105 
106             if (block != null && blockIndex >= block.bytes.length) {
107                 block = block.getNext();
108                 blockIndex = 0;
109             }
110 
111             if (null == block) {
112                 return -1;
113             }
114 
115             if (blockIndex >= block.bytes.length) {
116                 return -1;
117             }
118 
119             return 0xff & block.bytes[blockIndex++];
120         }
121 
122         @Override
123         public int read(final byte[] array, final int off, final int len) throws IOException {
124             // first section copied verbatim from InputStream
125             Objects.requireNonNull(array, "array");
126             if ((off < 0) || (off > array.length) || (len < 0)
127                     || ((off + len) > array.length) || ((off + len) < 0)) {
128                 throw new IndexOutOfBoundsException();
129             }
130             if (len == 0) {
131                 return 0;
132             }
133 
134             // optimized block read
135 
136             if (null == block) {
137                 if (readFirst) {
138                     return -1;
139                 }
140                 block = getFirstBlock();
141                 readFirst = true;
142             }
143 
144             if (block != null && blockIndex >= block.bytes.length) {
145                 block = block.getNext();
146                 blockIndex = 0;
147             }
148 
149             if (null == block) {
150                 return -1;
151             }
152 
153             if (blockIndex >= block.bytes.length) {
154                 return -1;
155             }
156 
157             final int readSize = Math.min(len, block.bytes.length - blockIndex);
158             System.arraycopy(block.bytes, blockIndex, array, off, readSize);
159             blockIndex += readSize;
160             return readSize;
161         }
162 
163         @Override
164         public long skip(final long n) throws IOException {
165 
166             long remaining = n;
167 
168             if (n <= 0) {
169                 return 0;
170             }
171 
172             while (remaining > 0) {
173                 // read the first block
174                 if (null == block) {
175                     if (readFirst) {
176                         return -1;
177                     }
178                     block = getFirstBlock();
179                     readFirst = true;
180                 }
181 
182                 // get next block
183                 if (block != null && blockIndex >= block.bytes.length) {
184                     block = block.getNext();
185                     blockIndex = 0;
186                 }
187 
188                 if (null == block) {
189                     break;
190                 }
191 
192                 if (blockIndex >= block.bytes.length) {
193                     break;
194                 }
195 
196                 final int readSize = Math.min((int) Math.min(BLOCK_SIZE, remaining), block.bytes.length - blockIndex);
197 
198                 blockIndex += readSize;
199                 remaining -= readSize;
200             }
201 
202             return n - remaining;
203         }
204 
205     }
206 
207     @Override
208     public InputStream getInputStream() throws IOException {
209         return new CacheReadingInputStream();
210     }
211 
212     @Override
213     public byte[] getBlock(final long blockStart, final int blockLength) throws IOException {
214         // We include a separate check for int overflow.
215         if ((blockStart < 0) || (blockLength < 0)
216                 || (blockStart + blockLength < 0)
217                 || (blockStart + blockLength > getLength())) {
218             throw new IOException("Could not read block (block start: "
219                     + blockStart + ", block length: " + blockLength
220                     + ", data length: " + streamLength + ").");
221         }
222 
223         final InputStream cis = getInputStream();
224         BinaryFunctions.skipBytes(cis, blockStart);
225 
226         final byte[] bytes = new byte[blockLength];
227         int total = 0;
228         while (true) {
229             final int read = cis.read(bytes, total, bytes.length - total);
230             if (read < 1) {
231                 throw new IOException("Could not read block.");
232             }
233             total += read;
234             if (total >= blockLength) {
235                 return bytes;
236             }
237         }
238     }
239 
240     @Override
241     public long getLength() throws IOException {
242         if (streamLength >= 0) {
243             return streamLength;
244         }
245 
246         final InputStream cis = getInputStream();
247         long result = 0;
248         long skipped;
249         while ((skipped = cis.skip(1024)) > 0) {
250             result += skipped;
251         }
252         streamLength = result;
253         return result;
254     }
255 
256     @Override
257     public byte[] getAll() throws IOException {
258         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
259 
260         CacheBlock block = getFirstBlock();
261         while (block != null) {
262             baos.write(block.bytes);
263             block = block.getNext();
264         }
265         return baos.toByteArray();
266     }
267 
268     @Override
269     public String getDescription() {
270         return "Inputstream: '" + getFileName() + "'";
271     }
272 
273 }