001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.common.bytesource;
018
019import java.io.BufferedInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.io.InputStream;
023
024import org.apache.commons.imaging.common.BinaryFunctions;
025
026public class ByteSourceInputStream extends ByteSource {
027    private static final int BLOCK_SIZE = 1024;
028
029    private final InputStream is;
030    private CacheBlock cacheHead;
031    private byte[] readBuffer;
032    private long streamLength = -1;
033
034    public ByteSourceInputStream(final InputStream is, final String filename) {
035        super(filename);
036        this.is = new BufferedInputStream(is);
037    }
038
039    private class CacheBlock {
040        public final byte[] bytes;
041        private CacheBlock next;
042        private boolean triedNext;
043
044        CacheBlock(final byte[] bytes) {
045            this.bytes = bytes;
046        }
047
048        public CacheBlock getNext() throws IOException {
049            if (null != next) {
050                return next;
051            }
052            if (triedNext) {
053                return null;
054            }
055            triedNext = true;
056            next = readBlock();
057            return next;
058        }
059
060    }
061
062    private CacheBlock readBlock() throws IOException {
063        if (null == readBuffer) {
064            readBuffer = new byte[BLOCK_SIZE];
065        }
066
067        final int read = is.read(readBuffer);
068        if (read < 1) {
069            return null;
070        } else if (read < BLOCK_SIZE) {
071            // return a copy.
072            final byte[] result = new byte[read];
073            System.arraycopy(readBuffer, 0, result, 0, read);
074            return new CacheBlock(result);
075        } else {
076            // return current buffer.
077            final byte[] result = readBuffer;
078            readBuffer = null;
079            return new CacheBlock(result);
080        }
081    }
082
083    private CacheBlock getFirstBlock() throws IOException {
084        if (null == cacheHead) {
085            cacheHead = readBlock();
086        }
087        return cacheHead;
088    }
089
090    private class CacheReadingInputStream extends InputStream {
091        private CacheBlock block;
092        private boolean readFirst;
093        private int blockIndex;
094
095        @Override
096        public int read() throws IOException {
097            if (null == block) {
098                if (readFirst) {
099                    return -1;
100                }
101                block = getFirstBlock();
102                readFirst = true;
103            }
104
105            if (block != null && blockIndex >= block.bytes.length) {
106                block = block.getNext();
107                blockIndex = 0;
108            }
109
110            if (null == block) {
111                return -1;
112            }
113
114            if (blockIndex >= block.bytes.length) {
115                return -1;
116            }
117
118            return 0xff & block.bytes[blockIndex++];
119        }
120
121        @Override
122        public int read(final byte[] b, final int off, final int len) throws IOException {
123            // first section copied verbatim from InputStream
124            if (b == null) {
125                throw new NullPointerException();
126            } else if ((off < 0) || (off > b.length) || (len < 0)
127                    || ((off + len) > b.length) || ((off + len) < 0)) {
128                throw new IndexOutOfBoundsException();
129            } else if (len == 0) {
130                return 0;
131            }
132
133            // optimized block read
134
135            if (null == block) {
136                if (readFirst) {
137                    return -1;
138                }
139                block = getFirstBlock();
140                readFirst = true;
141            }
142
143            if (block != null && blockIndex >= block.bytes.length) {
144                block = block.getNext();
145                blockIndex = 0;
146            }
147
148            if (null == block) {
149                return -1;
150            }
151
152            if (blockIndex >= block.bytes.length) {
153                return -1;
154            }
155
156            final int readSize = Math.min(len, block.bytes.length - blockIndex);
157            System.arraycopy(block.bytes, blockIndex, b, off, readSize);
158            blockIndex += readSize;
159            return readSize;
160        }
161
162        @Override
163        public long skip(final long n) throws IOException {
164
165            long remaining = n;
166
167            if (n <= 0) {
168                return 0;
169            }
170
171            while (remaining > 0) {
172                // read the first block
173                if (null == block) {
174                    if (readFirst) {
175                        return -1;
176                    }
177                    block = getFirstBlock();
178                    readFirst = true;
179                }
180
181                // get next block
182                if (block != null && blockIndex >= block.bytes.length) {
183                    block = block.getNext();
184                    blockIndex = 0;
185                }
186
187                if (null == block) {
188                    break;
189                }
190
191                if (blockIndex >= block.bytes.length) {
192                    break;
193                }
194
195                final int readSize = Math.min((int) Math.min(BLOCK_SIZE, remaining), block.bytes.length - blockIndex);
196
197                blockIndex += readSize;
198                remaining -= readSize;
199            }
200
201            return n - remaining;
202        }
203
204    }
205
206    @Override
207    public InputStream getInputStream() throws IOException {
208        return new CacheReadingInputStream();
209    }
210
211    @Override
212    public byte[] getBlock(final long blockStart, final int blockLength) throws IOException {
213        // We include a separate check for int overflow.
214        if ((blockStart < 0) || (blockLength < 0)
215                || (blockStart + blockLength < 0)
216                || (blockStart + blockLength > streamLength)) {
217            throw new IOException("Could not read block (block start: "
218                    + blockStart + ", block length: " + blockLength
219                    + ", data length: " + streamLength + ").");
220        }
221
222        final InputStream cis = getInputStream();
223        BinaryFunctions.skipBytes(cis, blockStart);
224
225        final byte[] bytes = new byte[blockLength];
226        int total = 0;
227        while (true) {
228            final int read = cis.read(bytes, total, bytes.length - total);
229            if (read < 1) {
230                throw new IOException("Could not read block.");
231            }
232            total += read;
233            if (total >= blockLength) {
234                return bytes;
235            }
236        }
237    }
238
239    @Override
240    public long getLength() throws IOException {
241        if (streamLength >= 0) {
242            return streamLength;
243        }
244
245        final InputStream cis = getInputStream();
246        long result = 0;
247        long skipped;
248        while ((skipped = cis.skip(1024)) > 0) {
249            result += skipped;
250        }
251        streamLength = result;
252        return result;
253    }
254
255    @Override
256    public byte[] getAll() throws IOException {
257        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
258
259        CacheBlock block = getFirstBlock();
260        while (block != null) {
261            baos.write(block.bytes);
262            block = block.getNext();
263        }
264        return baos.toByteArray();
265    }
266
267    @Override
268    public String getDescription() {
269        return "Inputstream: '" + getFilename() + "'";
270    }
271
272}