/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2009 Jaspersoft Corporation. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.jasperreports.engine.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
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 net.sf.jasperreports.engine.JRRuntimeException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author Lucian Chirita (lucianc@users.sourceforge.net)
* @version $Id: FileBufferedOutputStream.java 3717 2010-04-09 10:01:33Z teodord $
*/
public class FileBufferedOutputStream extends OutputStream
{
private static final Log log = LogFactory.getLog(FileBufferedOutputStream.class);
/**
* Specifies the maximum in-memory buffer length that triggers the creation of a temporary file on disk to store further content sent to this output stream.
*/
public static final String PROPERTY_MEMORY_THRESHOLD = JRProperties.PROPERTY_PREFIX + "file.buffer.os.memory.threshold";
//public static final int DEFAULT_MEMORY_THRESHOLD = 1 << 18;
public static final int INFINIT_MEMORY_THRESHOLD = -1;
public static final int DEFAULT_INITIAL_MEMORY_BUFFER_SIZE = 1 << 16;
public static final int DEFAULT_INPUT_BUFFER_LENGTH = 1 << 14;
private final int memoryThreshold;
private final int initialMemoryBufferSize;
private final int inputBufferLength;
private final ByteArrayOutputStream memoryOutput;
private int size;
private File file;
private BufferedOutputStream fileOutput;
private boolean closed;
private boolean disposed;
public FileBufferedOutputStream() {
this(JRProperties.getIntegerProperty(PROPERTY_MEMORY_THRESHOLD, INFINIT_MEMORY_THRESHOLD), DEFAULT_INITIAL_MEMORY_BUFFER_SIZE, DEFAULT_INPUT_BUFFER_LENGTH);
}
public FileBufferedOutputStream(int memoryThreshold) {
this(memoryThreshold, DEFAULT_INITIAL_MEMORY_BUFFER_SIZE, DEFAULT_INPUT_BUFFER_LENGTH);
}
public FileBufferedOutputStream(int memoryThreshold, int initialMemoryBufferSize) {
this(memoryThreshold, initialMemoryBufferSize, DEFAULT_INPUT_BUFFER_LENGTH);
}
public FileBufferedOutputStream(int memoryThreshold, int initialMemoryBufferSize, int inputBufferLength) {
this.memoryThreshold = memoryThreshold;
this.initialMemoryBufferSize = initialMemoryBufferSize;
this.inputBufferLength = inputBufferLength;
size = 0;
if (this.memoryThreshold == 0)
{
memoryOutput = null;
}
else
{
int initialSize = this.initialMemoryBufferSize;
if (initialSize > this.memoryThreshold)
{
initialSize = this.memoryThreshold;
}
memoryOutput = new ByteArrayOutputStream(initialSize);
}
}
public void write(int b) throws IOException {
checkClosed();
if (availableMemorySpace() > 0) {
memoryOutput.write(b);
} else {
ensureFileOutput().write(b);
}
++size;
}
protected int availableMemorySpace() {
int availableMemorySpace;
if (memoryOutput != null
&& (memoryThreshold < 0 || memoryOutput.size() < memoryThreshold)) {
availableMemorySpace = memoryThreshold - memoryOutput.size();
} else {
availableMemorySpace = 0;
}
return availableMemorySpace;
}
protected BufferedOutputStream ensureFileOutput() throws IOException, FileNotFoundException {
if (fileOutput == null) {
file = File.createTempFile("file.buff.os.", ".tmp");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutput = new BufferedOutputStream(fileOutputStream);
}
return fileOutput;
}
public void write(byte[] b, int off, int len) throws IOException {
checkClosed();
int memoryLen = availableMemorySpace();
if (len < memoryLen) {
memoryLen = len;
}
if (memoryLen > 0) {
memoryOutput.write(b, off, memoryLen);
}
if (memoryLen < len) {
ensureFileOutput().write(b, off + memoryLen, len - memoryLen);
}
size += len;
}
public void checkClosed() {
if (closed) {
throw new JRRuntimeException("Output stream already closed.");
}
}
public void close() throws IOException {
if (!closed && fileOutput != null) {
fileOutput.flush();
fileOutput.close();
}
closed = true;
}
public void flush() throws IOException {
if (fileOutput != null) {
fileOutput.flush();
}
}
public int size() {
return size;
}
public void writeData(OutputStream out) throws IOException {
if (!closed) {
close();
}
if (memoryOutput != null) {
memoryOutput.writeTo(out);
}
if (file != null) {
FileInputStream fileInput = new FileInputStream(file);
boolean inputClosed = false;
try {
byte[] buffer = new byte[inputBufferLength];
int read;
while((read = fileInput.read(buffer)) > 0) {
out.write(buffer, 0, read);
}
fileInput.close();
inputClosed = true;
} finally {
if (!inputClosed) {
try {
fileInput.close();
} catch (IOException e) {
log.warn("Could not close file input stream", e);
}
}
}
}
}
public void dispose() {
if (disposed) {
return;
}
boolean success = true;
if (!closed && fileOutput != null) {
try {
fileOutput.close();
} catch (IOException e) {
log.warn("Error while closing the temporary file output stream", e);
success = false;
}
}
if (file != null && !file.delete()) {
log.warn("Error while deleting the temporary file");
success = false;
}
disposed = success;
}
protected void finalize() throws Throwable //NOSONAR
{
dispose();
super.finalize();
}
public InputStream getDataInputStream() throws IOException
{
if (!closed)
{
close();
}
return new DataStream();
}
protected class DataStream extends InputStream
{
private int memoryIdx;
private final byte[] memoryData;
private final InputStream fileInput;
public DataStream() throws FileNotFoundException
{
memoryIdx = 0;
memoryData = memoryOutput == null ? new byte[0] : memoryOutput.toByteArray();
fileInput = file == null ? null : new BufferedInputStream(new FileInputStream(file));
}
public synchronized int read() throws IOException
{
int read;
if (memoryIdx < memoryData.length)
{
read = memoryData[memoryIdx] & 0xff;
++memoryIdx;
}
else if (fileInput != null)
{
read = fileInput.read();
}
else
{
read = -1;
}
return read;
}
public synchronized int read(byte b[], int off, int len) throws IOException
{
if (len <= 0)
{
return 0;
}
int read;
if (memoryIdx < memoryData.length)
{
read = len;
if (read > memoryData.length - memoryIdx)
{
read = memoryData.length - memoryIdx;
}
System.arraycopy(memoryData, memoryIdx, b, off, read);
memoryIdx += read;
}
else
{
read = 0;
}
if (read < len && fileInput != null)
{
int readFile = fileInput.read(b, off + read, len - read);
if (readFile > 0)
{
read += readFile;
}
}
return read == 0 ? -1 : read;
}
public void close() throws IOException
{
if (fileInput != null)
{
fileInput.close();
}
}
public synchronized int available() throws IOException
{
int available = memoryData.length - memoryIdx;
if (fileInput != null)
{
available += fileInput.available();
}
return available;
}
public synchronized long skip(long n) throws IOException
{
if (n <= 0)
{
return 0;
}
long skipped;
if (memoryIdx < memoryData.length)
{
skipped = n;
if (skipped > memoryData.length - memoryIdx)
{
skipped = memoryData.length - memoryIdx;
}
memoryIdx += skipped;
}
else
{
skipped = 0;
}
if (skipped < n && fileInput != null)
{
skipped += fileInput.skip(n - skipped);
}
return skipped;
}
}
}