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}