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 } 072 if (read < BLOCK_SIZE) { 073 // return a copy. 074 final byte[] result = new byte[read]; 075 System.arraycopy(readBuffer, 0, result, 0, read); 076 return new CacheBlock(result); 077 } 078 // return current buffer. 079 final byte[] result = readBuffer; 080 readBuffer = null; 081 return new CacheBlock(result); 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 } 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}