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.bytesource;
18  
19  import java.io.BufferedInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.util.Arrays;
23  import java.util.Objects;
24  
25  import org.apache.commons.imaging.ImagingException;
26  import org.apache.commons.imaging.common.Allocator;
27  import org.apache.commons.imaging.common.BinaryFunctions;
28  import org.apache.commons.io.IOUtils;
29  import org.apache.commons.io.build.AbstractOrigin.InputStreamOrigin;
30  
31  final class InputStreamByteSource extends ByteSource {
32  
33      private final class Block {
34  
35          public final byte[] bytes;
36          private Block next;
37          private boolean triedNext;
38  
39          Block(final byte[] bytes) {
40              this.bytes = bytes;
41          }
42  
43          public Block getNext() throws IOException {
44              if (null != next) {
45                  return next;
46              }
47              if (triedNext) {
48                  return null;
49              }
50              triedNext = true;
51              next = readBlock();
52              return next;
53          }
54  
55      }
56  
57      private final class BlockInputStream extends InputStream {
58          private Block block;
59          private boolean readFirst;
60          private int blockIndex;
61  
62          @Override
63          public int read() throws IOException {
64              if (null == block) {
65                  if (readFirst) {
66                      return -1;
67                  }
68                  block = getFirstBlock();
69                  readFirst = true;
70              }
71  
72              if (block != null && blockIndex >= block.bytes.length) {
73                  block = block.getNext();
74                  blockIndex = 0;
75              }
76  
77              if (null == block) {
78                  return -1;
79              }
80  
81              if (blockIndex >= block.bytes.length) {
82                  return -1;
83              }
84  
85              return 0xff & block.bytes[blockIndex++];
86          }
87  
88          @Override
89          public int read(final byte[] array, final int off, final int len) throws IOException {
90              // first section copied verbatim from InputStream
91              Objects.requireNonNull(array, "array");
92              if (off < 0 || off > array.length || len < 0 || off + len > array.length || off + len < 0) {
93                  throw new IndexOutOfBoundsException();
94              }
95              if (len == 0) {
96                  return 0;
97              }
98  
99              // optimized block read
100 
101             if (null == block) {
102                 if (readFirst) {
103                     return -1;
104                 }
105                 block = getFirstBlock();
106                 readFirst = true;
107             }
108 
109             if (block != null && blockIndex >= block.bytes.length) {
110                 block = block.getNext();
111                 blockIndex = 0;
112             }
113 
114             if (null == block) {
115                 return -1;
116             }
117 
118             if (blockIndex >= block.bytes.length) {
119                 return -1;
120             }
121 
122             final int readSize = Math.min(len, block.bytes.length - blockIndex);
123             System.arraycopy(block.bytes, blockIndex, array, off, readSize);
124             blockIndex += readSize;
125             return readSize;
126         }
127 
128         @Override
129         public long skip(final long n) throws IOException {
130 
131             long remaining = n;
132 
133             if (n <= 0) {
134                 return 0;
135             }
136 
137             while (remaining > 0) {
138                 // read the first block
139                 if (null == block) {
140                     if (readFirst) {
141                         return -1;
142                     }
143                     block = getFirstBlock();
144                     readFirst = true;
145                 }
146 
147                 // get next block
148                 if (block != null && blockIndex >= block.bytes.length) {
149                     block = block.getNext();
150                     blockIndex = 0;
151                 }
152 
153                 if (null == block) {
154                     break;
155                 }
156 
157                 if (blockIndex >= block.bytes.length) {
158                     break;
159                 }
160 
161                 final int readSize = Math.min((int) Math.min(BLOCK_SIZE, remaining), block.bytes.length - blockIndex);
162 
163                 blockIndex += readSize;
164                 remaining -= readSize;
165             }
166 
167             return n - remaining;
168         }
169 
170     }
171 
172     private static final int BLOCK_SIZE = 1024;
173     private final InputStream inputStream;
174     private Block headBlock;
175     private byte[] readBuffer;
176     private long streamLength = -1;
177 
178     InputStreamByteSource(final InputStream inputStream, final String fileName) {
179         super(new InputStreamOrigin(inputStream), fileName);
180         this.inputStream = new BufferedInputStream(inputStream);
181     }
182 
183     @Override
184     public byte[] getByteArray(final long position, final int length) throws IOException {
185         // We include a separate check for int overflow.
186         if (position < 0 || length < 0 || position + length < 0 || position + length > size()) {
187             throw new ImagingException(
188                     "Could not read block (block start: " + position + ", block length: " + length + ", data length: " + streamLength + ").");
189         }
190 
191         final InputStream cis = getInputStream();
192         BinaryFunctions.skipBytes(cis, position);
193 
194         final byte[] bytes = Allocator.byteArray(length);
195         int total = 0;
196         while (true) {
197             final int read = cis.read(bytes, total, bytes.length - total);
198             if (read < 1) {
199                 throw new ImagingException("Could not read block.");
200             }
201             total += read;
202             if (total >= length) {
203                 return bytes;
204             }
205         }
206     }
207 
208     private Block getFirstBlock() throws IOException {
209         if (null == headBlock) {
210             headBlock = readBlock();
211         }
212         return headBlock;
213     }
214 
215     @Override
216     public InputStream getInputStream() throws IOException {
217         return new BlockInputStream();
218     }
219 
220     private Block readBlock() throws IOException {
221         if (null == readBuffer) {
222             readBuffer = new byte[BLOCK_SIZE];
223         }
224 
225         final int read = inputStream.read(readBuffer);
226         if (read < 1) {
227             return null;
228         }
229         if (read < BLOCK_SIZE) {
230             // return a copy.
231             return new Block(Arrays.copyOf(readBuffer, read));
232         }
233         // return current buffer.
234         final byte[] result = readBuffer;
235         readBuffer = null;
236         return new Block(result);
237     }
238 
239     @Override
240     public long size() throws IOException {
241         if (streamLength >= 0) {
242             return streamLength;
243         }
244 
245         try (InputStream cis = getInputStream()) {
246             final long result = IOUtils.consume(cis);
247             streamLength = result;
248             return result;
249         }
250     }
251 
252 }