001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 the ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| 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.conqat.lib.commons.io; 018 019import java.io.OutputStream; 020import java.util.ArrayList; 021import java.util.List; 022 023import org.conqat.lib.commons.assertion.CCSMAssert; 024 025/** 026 * A stream that provides functionality for writing to a list of byte arrays, 027 * also called chunks. 028 */ 029public class ChunkOutputStream extends OutputStream { 030 031 /** The chunk size to use. */ 032 private int chunkSize; 033 034 /** The list of written chunks. */ 035 private List<byte[]> chunks; 036 037 /** The chunk to which is currently written. */ 038 private byte[] currentChunk; 039 040 /** The offset in the current chunk to write to. */ 041 private int currentOffset; 042 043 /** 044 * Creates a new ChunkOutputStream with a chunk size of pow(2,20). 045 */ 046 public ChunkOutputStream() { 047 this(1 << 20); 048 } 049 050 /** 051 * Creates a new ChunkOutputStream with the given chunk size. 052 * 053 * @param chunkSize 054 * the chunk size 055 */ 056 public ChunkOutputStream(int chunkSize) { 057 CCSMAssert.isTrue(chunkSize >= 1, "chunkSize must be >= 1, is " + chunkSize); 058 this.chunkSize = chunkSize; 059 this.currentOffset = 0; 060 061 chunks = new ArrayList<>(); 062 } 063 064 /** Returns chunk size. */ 065 public int getChunkSize() { 066 return chunkSize; 067 } 068 069 /** {@inheritDoc} */ 070 @Override 071 public void write(int b) { 072 updateChunk(); 073 currentChunk[currentOffset++] = (byte) b; 074 } 075 076 /** {@inheritDoc} */ 077 @Override 078 public void write(byte[] b, int offset, int length) { 079 int alreadyWritten = 0; 080 while (alreadyWritten < length) { 081 updateChunk(); 082 083 int writeNow = Math.min(length - alreadyWritten, chunkSize - currentOffset); 084 System.arraycopy(b, offset + alreadyWritten, currentChunk, currentOffset, writeNow); 085 086 currentOffset += writeNow; 087 alreadyWritten += writeNow; 088 } 089 } 090 091 /** 092 * Updates the current chunk. If the current offset is at the end of the 093 * current chunk, switch to the next one. 094 */ 095 private void updateChunk() { 096 if (currentOffset == chunkSize || chunks.isEmpty()) { 097 currentChunk = new byte[chunkSize]; 098 chunks.add(currentChunk); 099 currentOffset = 0; 100 } 101 } 102 103 /** Returns chunks. */ 104 public List<byte[]> getChunks() { 105 return chunks; 106 } 107 108 /** Returns the size of the last chunk. */ 109 public int getLastChunkSize() { 110 return currentOffset; 111 } 112 113 /** {@inheritDoc} */ 114 @Override 115 public void close() { 116 // do nothing 117 } 118}