    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;
    023import java.util.Objects;
    024
    025import org.apache.commons.imaging.common.BinaryFunctions;
    026
    027public class ByteSourceInputStream extends ByteSource {
    028    private static final int BLOCK_SIZE = 1024;
    029
    030    private final InputStream is;
    031    private CacheBlock cacheHead;
    032    private byte[] readBuffer;
    033    private long streamLength = -1;
    034
    035    public ByteSourceInputStream(final InputStream is, final String fileName) {
    036        super(fileName);
    037        this.is = new BufferedInputStream(is);
    038    }
    039
    040    private class CacheBlock {
    041        public final byte[] bytes;
    042        private CacheBlock next;
    043        private boolean triedNext;
    044
    045        CacheBlock(final byte[] bytes) {
    046            this.bytes = bytes;
    047        }
    048
    049        public CacheBlock getNext() throws IOException {
    050            if (null != next) {
    051                return next;
    052            }
    053            if (triedNext) {
    054                return null;
    055            }
    056            triedNext = true;
    057            next = readBlock();
    058            return next;
    059        }
    060
    061    }
    062
    063    private CacheBlock readBlock() throws IOException {
    064        if (null == readBuffer) {
    065            readBuffer = new byte[BLOCK_SIZE];
    066        }
    067
    068        final int read = is.read(readBuffer);
    069        if (read < 1) {
    070            return null;
    071        } else if (read < BLOCK_SIZE) {
    072            // return a copy.
    073            final byte[] result = new byte[read];
    074            System.arraycopy(readBuffer, 0, result, 0, read);
    075            return new CacheBlock(result);
    076        } else {
    077            // return current buffer.
    078            final byte[] result = readBuffer;
    079            readBuffer = null;
    080            return new CacheBlock(result);
    081        }
    082    }
    083
    084    private CacheBlock getFirstBlock() throws IOException {
    085        if (null == cacheHead) {
    086            cacheHead = readBlock();
    087        }
    088        return cacheHead;
    089    }
    090
    091    private class CacheReadingInputStream extends InputStream {
    092        private CacheBlock block;
    093        private boolean readFirst;
    094        private int blockIndex;
    095
    096        @Override
    097        public int read() throws IOException {
    098            if (null == block) {
    099                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            } 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, array, 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 > getLength())) {
    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}