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