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}