Java tutorial
/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache * License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tradefed.util; import com.google.common.io.CountingOutputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.SequenceInputStream; /** * A thread safe file backed {@link OutputStream} that limits the maximum amount of data that can be * written. * <p/> * This is implemented by keeping a circular list of Files of fixed size. Once a File has reached a * certain size, the class jumps to use the next File in the list. If the next File is non empty, it * is deleted, and a new file created. */ public class SizeLimitedOutputStream extends OutputStream { private static final int DEFAULT_NUM_TMP_FILES = 5; /** The max number of bytes to store in the buffer */ private static final int BUFF_SIZE = 32 * 1024; // circular array of backing files private final File[] mFiles; private final long mMaxFileSize; private CountingOutputStream mCurrentOutputStream; private int mCurrentFilePos = 0; private final String mTempFilePrefix; private final String mTempFileSuffix; /** * Creates a {@link SizeLimitedOutputStream}. * * @param maxDataSize the approximate max size in bytes to keep in the output stream * @param numFiles the max number of backing files to use to store data. Higher values will mean * max data kept will be close to maxDataSize, but with a possible performance * penalty. * @param tempFilePrefix prefix to use for temporary files * @param tempFileSuffix suffix to use for temporary files */ public SizeLimitedOutputStream(long maxDataSize, int numFiles, String tempFilePrefix, String tempFileSuffix) { mMaxFileSize = maxDataSize / numFiles; mFiles = new File[numFiles]; mCurrentFilePos = numFiles; mTempFilePrefix = tempFilePrefix; mTempFileSuffix = tempFileSuffix; } /** * Creates a {@link SizeLimitedOutputStream} with default number of backing files. * * @param maxDataSize the approximate max size to keep in the output stream * @param tempFilePrefix prefix to use for temporary files * @param tempFileSuffix suffix to use for temporary files */ public SizeLimitedOutputStream(long maxDataSize, String tempFilePrefix, String tempFileSuffix) { this(maxDataSize, DEFAULT_NUM_TMP_FILES, tempFilePrefix, tempFileSuffix); } /** * Gets the collected output as a {@link InputStream}. * <p/> * It is recommended to buffer returned stream before using. * * @return The collected output as a {@link InputStream}. */ public synchronized InputStream getData() throws IOException { flush(); InputStream combinedStream = null; for (int i = 0; i < mFiles.length; i++) { // oldest/starting file is always the next one up from current int currentPos = (mCurrentFilePos + i + 1) % mFiles.length; if (mFiles[currentPos] != null) { @SuppressWarnings("resource") FileInputStream fStream = new FileInputStream(mFiles[currentPos]); if (combinedStream == null) { combinedStream = fStream; } else { combinedStream = new SequenceInputStream(combinedStream, fStream); } } } if (combinedStream == null) { combinedStream = new ByteArrayInputStream(new byte[0]); } return combinedStream; } /** * {@inheritDoc} */ @Override public synchronized void flush() { if (mCurrentOutputStream == null) { return; } try { mCurrentOutputStream.flush(); } catch (IOException e) { // don't use CLog in this class, because its the underlying stream for the logger. // leads to bad things System.out.printf("failed to flush data: %s\n", e); } } /** * Delete all accumulated data. */ public void delete() { close(); for (int i = 0; i < mFiles.length; i++) { FileUtil.deleteFile(mFiles[i]); mFiles[i] = null; } } /** * Closes the write stream */ @Override public synchronized void close() { StreamUtil.flushAndCloseStream(mCurrentOutputStream); mCurrentOutputStream = null; } /** * Creates a new tmp file, closing the old one as necessary * <p> * Exposed for unit testing. * </p> * * @throws IOException * @throws FileNotFoundException */ synchronized void generateNextFile() throws IOException, FileNotFoundException { // close current stream close(); mCurrentFilePos = getNextIndex(mCurrentFilePos); FileUtil.deleteFile(mFiles[mCurrentFilePos]); mFiles[mCurrentFilePos] = FileUtil.createTempFile(mTempFilePrefix, mTempFileSuffix); mCurrentOutputStream = new CountingOutputStream( new BufferedOutputStream(new FileOutputStream(mFiles[mCurrentFilePos]), BUFF_SIZE)); } /** * Gets the next index to use for <var>mFiles</var>, treating it as a circular list. */ private int getNextIndex(int i) { return (i + 1) % mFiles.length; } @Override public synchronized void write(int data) throws IOException { if (mCurrentOutputStream == null) { generateNextFile(); } mCurrentOutputStream.write(data); if (mCurrentOutputStream.getCount() >= mMaxFileSize) { generateNextFile(); } } @Override public synchronized void write(byte[] b, int off, int len) throws IOException { if (mCurrentOutputStream == null) { generateNextFile(); } // keep writing to output stream as long as we have something to write while (len > 0) { // get current output stream size long currentSize = mCurrentOutputStream.getCount(); // get how many more we can write into current long freeSpace = mMaxFileSize - currentSize; // decide how much we should write: either fill up free space, or write entire content long sizeToWrite = freeSpace > len ? len : freeSpace; mCurrentOutputStream.write(b, off, (int) sizeToWrite); // accounting of space left, where to write next freeSpace -= sizeToWrite; off += sizeToWrite; len -= sizeToWrite; if (freeSpace <= 0) { generateNextFile(); } } } }