001package com.hfg.util.io; 002 003 004import java.io.BufferedInputStream; 005import java.io.ByteArrayInputStream; 006import java.io.ByteArrayOutputStream; 007import java.io.IOException; 008import java.io.InputStream; 009import java.io.OutputStream; 010import java.util.ArrayList; 011import java.util.Arrays; 012import java.util.Collection; 013import java.util.List; 014import java.util.zip.ZipEntry; 015import java.util.zip.ZipOutputStream; 016 017import com.hfg.exception.ProgrammingException; 018import com.hfg.util.collection.CollectionUtil; 019 020//------------------------------------------------------------------------------ 021/** 022 Container for a file name plus file contents as bytes. 023 Useful for holding temp files created in memory before producing a zip archive. 024 <div> 025 @author J. Alex Taylor, hairyfatguy.com 026 </div> 027 */ 028//------------------------------------------------------------------------------ 029// com.hfg XML/HTML Coding Library 030// 031// This library is free software; you can redistribute it and/or 032// modify it under the terms of the GNU Lesser General Public 033// License as published by the Free Software Foundation; either 034// version 2.1 of the License, or (at your option) any later version. 035// 036// This library is distributed in the hope that it will be useful, 037// but WITHOUT ANY WARRANTY; without even the implied warranty of 038// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 039// Lesser General Public License for more details. 040// 041// You should have received a copy of the GNU Lesser General Public 042// License along with this library; if not, write to the Free Software 043// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 044// 045// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 046// jataylor@hairyfatguy.com 047//------------------------------------------------------------------------------ 048 049public class FileBytes extends OutputStream 050{ 051 private String mName; 052// List<byte[]> mDataChunks; 053 private Long mLength; 054 055 private List<byte[]> mCompressedDataChunks; 056 private byte[] mUncompressedDataChunk; 057 private int mUncompressedDataChunkSize; 058 059 private static int sDefaultChunkSize = 8192; 060 061 //########################################################################### 062 // CONSTRUCTORS 063 //########################################################################### 064 065 //-------------------------------------------------------------------------- 066 public FileBytes(String inName) 067 { 068 mName = inName; 069 } 070 071 //########################################################################### 072 // PUBLIC METHODS 073 //########################################################################### 074 075 //-------------------------------------------------------------------------- 076 public String name() 077 { 078 return mName; 079 } 080 081 //-------------------------------------------------------------------------- 082 public long length() 083 { 084 return mLength != null ? mLength : 0; 085 } 086 087 //-------------------------------------------------------------------------- 088 public FileBytes setData(byte[] inData) 089 throws IOException 090 { 091 if (inData != null) 092 { 093 setData(new ByteArrayInputStream(inData)); 094 } 095 else 096 { 097 clearData(); 098 } 099 100 return this; 101 } 102 103 //-------------------------------------------------------------------------- 104 public FileBytes setData(InputStream inStream) 105 throws IOException 106 { 107 clearData(); 108 109 if (inStream != null) 110 { 111 mCompressedDataChunks = new ArrayList<>(50); 112 BufferedInputStream bufferedStream = null; 113 try 114 { 115 bufferedStream = new BufferedInputStream(inStream); 116 117 byte[] readBuffer = new byte[4096]; 118 int readSize = 0; 119 while ((readSize = bufferedStream.read(readBuffer)) >= 0) 120 { 121 mCompressedDataChunks.add(GZIP.compress(readBuffer, 0, readSize)); 122 mLength += readSize; 123 } 124 } 125 finally 126 { 127 if (bufferedStream != null) 128 { 129 bufferedStream.close(); 130 } 131 } 132 } 133 134 return this; 135 } 136 137 138 //-------------------------------------------------------------------------- 139 public void write(int inByte) 140 throws IOException 141 { 142 appendData(inByte); 143 } 144 145 //-------------------------------------------------------------------------- 146 public void write(byte[] inBytes) 147 throws IOException 148 { 149 appendData(inBytes); 150 } 151 152 //-------------------------------------------------------------------------- 153 public void write(byte[] inBytes, int inOffset, int inLength) 154 throws IOException 155 { 156 appendData(inBytes, inOffset, inLength); 157 } 158 159 //-------------------------------------------------------------------------- 160 public void appendData(int inByte) 161 throws IOException 162 { 163 if (null == mUncompressedDataChunk) 164 { 165 mUncompressedDataChunk = new byte[sDefaultChunkSize]; 166 mUncompressedDataChunkSize = 0; 167 } 168 169 mUncompressedDataChunk[mUncompressedDataChunkSize++] = (byte) inByte; 170 if (mUncompressedDataChunkSize == mUncompressedDataChunk.length) 171 { 172 if (null == mCompressedDataChunks) 173 { 174 mCompressedDataChunks = new ArrayList<>(50); 175 } 176 177 mCompressedDataChunks.add(GZIP.compress(mUncompressedDataChunk, 0, mUncompressedDataChunkSize)); 178 mUncompressedDataChunkSize = 0; 179 } 180 else 181 { 182 mUncompressedDataChunkSize++; 183 } 184 185 if (mLength != null) 186 { 187 mLength++; 188 } 189 else 190 { 191 mLength = 1L; 192 } 193 } 194 195 //-------------------------------------------------------------------------- 196 public FileBytes appendData(byte[] inBytes) 197 throws IOException 198 { 199 return appendData(inBytes, 0, inBytes.length); 200 } 201 202 //-------------------------------------------------------------------------- 203 public FileBytes appendData(byte[] inBytes, int inOffset, int inLength) 204 throws IOException 205 { 206 if (null == mUncompressedDataChunk) 207 { 208 mUncompressedDataChunk = new byte[sDefaultChunkSize]; 209 mUncompressedDataChunkSize = 0; 210 } 211 212 int totalBytesAdded = 0; 213 while (totalBytesAdded < inLength) 214 { 215 int lengthRemainingToCopy = inLength - totalBytesAdded; 216 217 int bytesCopied = Math.min(lengthRemainingToCopy, mUncompressedDataChunk.length - mUncompressedDataChunkSize); 218 System.arraycopy(inBytes, inOffset, mUncompressedDataChunk, mUncompressedDataChunkSize, bytesCopied); 219 220 if (mUncompressedDataChunkSize == mUncompressedDataChunk.length) 221 { 222 if (null == mCompressedDataChunks) 223 { 224 mCompressedDataChunks = new ArrayList<>(50); 225 } 226 227 mCompressedDataChunks.add(GZIP.compress(mUncompressedDataChunk, 0, mUncompressedDataChunkSize)); 228 mUncompressedDataChunkSize = 0; 229 } 230 else 231 { 232 mUncompressedDataChunkSize += bytesCopied; 233 } 234 235 totalBytesAdded += bytesCopied; 236 } 237 238 if (mLength != null) 239 { 240 mLength += inBytes.length; 241 } 242 else 243 { 244 mLength = (long) inLength; 245 } 246 247 return this; 248 } 249 250 //-------------------------------------------------------------------------- 251 public FileBytes appendData(FileBytes inFileBytes) 252 throws IOException 253 { 254 if (inFileBytes.length() > 0) 255 { 256 if (null == mCompressedDataChunks) 257 { 258 mCompressedDataChunks = new ArrayList<>(50); 259 } 260 261 // Add whatever we have sitting uncompressed at the moment 262 if (mUncompressedDataChunkSize > 0) 263 { 264 mCompressedDataChunks.add(GZIP.compress(mUncompressedDataChunk, 0, mUncompressedDataChunkSize)); 265 mUncompressedDataChunkSize = 0; 266 } 267 268 // Add the new compressed chunks 269 for (byte[] bytes : inFileBytes.mCompressedDataChunks) 270 { 271 mCompressedDataChunks.add(bytes); 272 } 273 274 // Add the new uncompressed chunk 275 if (inFileBytes.mUncompressedDataChunk != null) 276 { 277 mUncompressedDataChunk = Arrays.copyOf(inFileBytes.mUncompressedDataChunk, sDefaultChunkSize); 278 mUncompressedDataChunkSize = inFileBytes.mUncompressedDataChunkSize; 279 } 280 else 281 { 282 mUncompressedDataChunkSize = 0; 283 } 284 285 if (mLength != null) 286 { 287 mLength += inFileBytes.length(); 288 } 289 else 290 { 291 mLength = inFileBytes.length(); 292 } 293 } 294 295 return this; 296 } 297 298 //-------------------------------------------------------------------------- 299 public byte[] getBytes() 300 { 301 ByteArrayOutputStream stream; 302 try 303 { 304 stream = new ByteArrayOutputStream(); 305 StreamUtil.writeToStream(getDataStream(), stream); 306 } 307 catch (IOException e) 308 { 309 throw new ProgrammingException(e); 310 } 311 312 return stream.toByteArray(); 313 } 314 315 //-------------------------------------------------------------------------- 316 public InputStream getDataStream() 317 { 318 return new DataStreamer(); 319 } 320 321 //-------------------------------------------------------------------------- 322 public void writeData(OutputStream inStream) 323 throws IOException 324 { 325 if (CollectionUtil.hasValues(mCompressedDataChunks)) 326 { 327 for (byte[] chunk : mCompressedDataChunks) 328 { 329 inStream.write(GZIP.uncompress(chunk)); 330 } 331 } 332 333 if (mUncompressedDataChunkSize > 0) 334 { 335 inStream.write(mUncompressedDataChunk, 0, mUncompressedDataChunkSize); 336 } 337 338 inStream.flush(); 339 } 340 341 //-------------------------------------------------------------------------- 342 public static void streamAsZipFile(OutputStream inWriteTarget, String inBaseZipFileName, Collection<FileBytes> inFiles) 343 throws IOException 344 { 345 ZipOutputStream zipStream = null; 346 try 347 { 348 zipStream = new ZipOutputStream(inWriteTarget); 349 350 for (FileBytes file : inFiles) 351 { 352 ZipEntry zipEntry = new ZipEntry(inBaseZipFileName + "/" + file.name()); 353 zipStream.putNextEntry(zipEntry); 354 file.writeData(zipStream); 355 zipStream.closeEntry(); 356 } 357 } 358 finally 359 { 360 if (zipStream != null) 361 { 362 zipStream.finish(); 363 } 364 } 365 } 366 367 368 //########################################################################### 369 // PRIVATE METHODS 370 //########################################################################### 371 372 //-------------------------------------------------------------------------- 373 private void clearData() 374 { 375 mCompressedDataChunks = null; 376 mLength = 0L; 377 } 378 379 //########################################################################### 380 // INNER CLASS 381 //########################################################################### 382 383 private class DataStreamer extends InputStream 384 { 385 private byte[] mCurrentCompressedChunk; 386 private int mCurrentCompressedChunkIndex; 387 private int mUncompressedChunkIndex; 388 private int mByteIndex; 389 private boolean mDone = false; 390 391 int count = 0; 392 //----------------------------------------------------------------------- 393 public DataStreamer() 394 { 395 mCurrentCompressedChunkIndex = 0; 396 } 397 398 //----------------------------------------------------------------------- 399 public int read() 400 { 401 return (mDone ? -1 : getNextByte()); 402 } 403 404 405 //----------------------------------------------------------------------- 406 @Override 407 public int read(byte b[], int off, int len) 408 throws IOException 409 { 410 if (b == null) 411 { 412 throw new NullPointerException(); 413 } 414 else if (off < 0 || len < 0 || len > b.length - off) 415 { 416 throw new IndexOutOfBoundsException(); 417 } 418 else if (len == 0) 419 { 420 return 0; 421 } 422 423 int c = read(); 424 if (c == -1 && mDone) 425 { 426 return -1; 427 } 428 b[off] = (byte) c; 429 430 int i = 1; 431 for (; i < len; i++) 432 { 433 c = read(); 434 if (c == -1 && mDone) 435 { 436 break; 437 } 438 b[off + i] = (byte) c; 439 } 440 441 return i; 442 } 443 444 //----------------------------------------------------------------------- 445 private byte getNextByte() 446 { 447 byte nextByte = -1; 448 449 if (null == mCurrentCompressedChunk 450 && mCompressedDataChunks != null) 451 { 452 mCurrentCompressedChunk = GZIP.uncompress(mCompressedDataChunks.get(mCurrentCompressedChunkIndex)); 453 mByteIndex = 0; 454 } 455 456 if (null == mCurrentCompressedChunk) 457 { 458 // Done with compressed chunks. Read from the uncompressed chunk. 459 nextByte = mUncompressedDataChunk[mUncompressedChunkIndex++]; 460 if (mUncompressedChunkIndex >= mUncompressedDataChunkSize) 461 { 462 // This was the last chunk. 463 mDone = true; 464 } 465 } 466 else 467 { 468 nextByte = mCurrentCompressedChunk[mByteIndex++]; 469 470 if (mByteIndex >= mCurrentCompressedChunk.length) 471 { 472 // This is the last byte in this chunk. 473 mCurrentCompressedChunk = null; 474 mCurrentCompressedChunkIndex++; 475 if (mCurrentCompressedChunkIndex < 0 || mCurrentCompressedChunkIndex == mCompressedDataChunks.size()) 476 { 477 if (mUncompressedDataChunkSize > 0) 478 { 479 mUncompressedChunkIndex = 0; 480 } 481 else 482 { 483 // This was the last chunk. 484 mDone = true; 485 } 486 } 487 } 488 } 489 490 return nextByte; 491 } 492 } 493 494}