All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.norconex.commons.lang.io.CachedOutputStream Maven / Gradle / Ivy

Go to download

Norconex Commons Lang is a Java library containing utility classes that complements the Java API and are not found in commonly available libraries (such as the great Apache Commons Lang, which it relies on).

There is a newer version: 2.0.2
Show newest version
/* Copyright 2014-2016 Norconex Inc.
 *
 * 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.norconex.commons.lang.io;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import com.norconex.commons.lang.file.FileUtil;
import com.norconex.commons.lang.io.CachedStreamFactory.MemoryTracker;

/**
 * {@link OutputStream} wrapper that caches the output so it can be retrieved
 * once as a {@link CachedInputStream}. Invoking {@link #getInputStream()}
 * effectively {@link #close()} this stream and it can no longer be written
 * to.  Obtaining an input stream before or instead of calling the close
 * method will not delete the cache content, but rather pass the reference 
 * to it to the CachedInputStream. 
 * 

* To create new instances of {@link CachedOutputStream}, use the * {@link CachedStreamFactory} class. Reusing the same factory * will ensure all {@link CachedOutputStream} instances created share the same * combined maximum memory. Invoking one of the * newOutputStream(...) methods on this class have the same effect. *

* The internal cache stores written bytes into memory, up to to the * specified maximum cache size. If content exceeds * the cache limit, the cache transforms itself into a file-based cache * of unlimited size. Default memory cache size is 128 KB. *

* @author Pascal Essiembre * @since 1.5 * @see CachedStreamFactory */ public class CachedOutputStream extends OutputStream implements ICachedStream { private static final Logger LOG = LogManager.getLogger(CachedOutputStream.class); private final CachedStreamFactory factory; private final MemoryTracker tracker; private OutputStream outputStream; private byte[] memCache; private ByteArrayOutputStream memOutputStream; private File fileCache; private OutputStream fileOutputStream; private boolean doneWriting = false; private boolean closed = false; private boolean cacheEmpty = true; private final File cacheDirectory; //--- Constructors --------------------------------------------------------- /** * Caches the wrapped OutputStream. The wrapped stream * will be written to as expected, but its content will be cached in this * instance. * @param factory Cached stream factory * @param out OutputStream to cache * @param cacheDirectory directory where to store large content */ /*default*/ CachedOutputStream(CachedStreamFactory factory, OutputStream out, File cacheDirectory) { super(); this.factory = factory; this.tracker = factory.new MemoryTracker(); memOutputStream = new ByteArrayOutputStream(); if (out != null) { if (out instanceof BufferedOutputStream) { this.outputStream = out; } else { this.outputStream = new BufferedOutputStream(out); } } if (cacheDirectory == null) { this.cacheDirectory = FileUtils.getTempDirectory(); } else { this.cacheDirectory = cacheDirectory; } } //--- Methods -------------------------------------------------------------- @Override public void write(int b) throws IOException { if (doneWriting) { throw new IllegalStateException( "Cannot write to this closed output stream."); } if (outputStream != null) { outputStream.write(b); } if (fileOutputStream != null) { // Write to file cache fileOutputStream.write(b); } else if (!tracker.hasEnoughAvailableMemory(memOutputStream, 1)) { // Too big: create file cache and write to it. cacheToFile(); fileOutputStream.write(b); } else { // Write to memory cache memOutputStream.write(b); } cacheEmpty = false; } @Override public void write(byte[] b, int off, int len) throws IOException { if (doneWriting) { throw new IllegalStateException( "Cannot write to this closed output stream."); } if (outputStream != null) { outputStream.write(b, off, len); } if (fileOutputStream != null) { fileOutputStream.write(b, off, len); } else if (!tracker.hasEnoughAvailableMemory( memOutputStream, len - off)) { cacheToFile(); fileOutputStream.write(b, off, len); } else { memOutputStream.write(b, 0, len); } cacheEmpty = false; } public CachedInputStream getInputStream() throws IOException { if (closed) { throw new IllegalStateException("Cannot get CachedInputStream on a " + "closed CachedOutputStream."); } CachedInputStream is; if (fileCache != null) { is = factory.newInputStream(fileCache); } else if (memCache != null) { is = factory.newInputStream(memCache); } else { memCache = memOutputStream.toByteArray(); memOutputStream.close(); memOutputStream = null; is = factory.newInputStream(memCache); } close(false); return is; } private void close(boolean clearCache) throws IOException { if (!closed) { closed = true; if (memCache != null && clearCache) { memCache = null; } if (outputStream != null) { outputStream.flush(); IOUtils.closeQuietly(outputStream); outputStream = null; } if (fileOutputStream != null) { fileOutputStream.flush(); IOUtils.closeQuietly(fileOutputStream); fileOutputStream = null; } if (fileCache != null && clearCache) { FileUtil.delete(fileCache); LOG.debug("Deleted cache file: " + fileCache); fileCache = null; } if (memOutputStream != null && clearCache) { memOutputStream.flush(); memOutputStream.close(); memOutputStream = null; } cacheEmpty = true; } } @Override public void close() throws IOException { close(true); } /** * Gets the cache directory where temporary cache files are created. * @return the cache directory */ public final File getCacheDirectory() { return cacheDirectory; } public CachedStreamFactory getStreamFactory() { return factory; } /** * Returns true if was nothing to cache (no writing was * performed) or if the stream was closed. * @return true if empty */ public boolean isCacheEmpty() { return cacheEmpty; } public CachedOutputStream newOuputStream(OutputStream os) { return factory.newOuputStream(os); } public CachedOutputStream newOuputStream() { return factory.newOuputStream(); } @Override public long getMemCacheSize() { if (memCache != null) { return memCache.length; } if (memOutputStream != null) { return memOutputStream.size(); } return 0; } @SuppressWarnings("resource") private void cacheToFile() throws IOException { fileCache = File.createTempFile( "CachedOutputStream-", "-temp", cacheDirectory); fileCache.deleteOnExit(); LOG.debug("Reached max cache size. Swapping to file: " + fileCache); RandomAccessFile f = new RandomAccessFile(fileCache, "rw"); FileChannel channel = f.getChannel(); fileOutputStream = Channels.newOutputStream(channel); IOUtils.write(memOutputStream.toByteArray(), fileOutputStream); memOutputStream = null; } @Override protected void finalize() throws Throwable { close(); super.finalize(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy