blob: 96859bb2ae09383cd09ad18243d1ba31f47b4e5d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.sling.distribution.util.impl;
import static java.io.File.createTempFile;
import static java.lang.Math.pow;
import static java.nio.ByteBuffer.allocate;
import static java.nio.ByteBuffer.allocateDirect;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* {@link OutputStream} implementation which writes into a {@code byte[]} until a certain threshold is
* reached and then starts writing into a {@code File} beyond that.
*/
public class FileBackedMemoryOutputStream extends OutputStream {
private int memorySize = -1;
public enum MemoryUnit {
BYTES(1),
KILO_BYTES(1000),
MEGA_BYTES((int) pow(10, 6)),
GIGA_BYTES((int) pow(10, 9));
private final int memoryFactor;
MemoryUnit(int memoryFactor) {
this.memoryFactor = memoryFactor;
}
}
private final ByteBuffer memory;
private final File tempDirectory;
private final String fileName;
private final String fileExtension;
private FileOutputStream out;
private File file;
public FileBackedMemoryOutputStream(int fileThreshold,
MemoryUnit memoryUnit,
boolean useOffHeapMemory,
File tempDirectory,
String fileName,
String fileExtension) {
if (fileThreshold < 0) {
throw new IllegalArgumentException("Negative fileThreshold size has no semantic in this version.");
}
int threshold = fileThreshold * memoryUnit.memoryFactor;
if (useOffHeapMemory) {
memory = allocateDirect(threshold);
} else {
memory = allocate(threshold);
}
this.tempDirectory = tempDirectory;
this.fileName = fileName;
this.fileExtension = fileExtension;
}
@Override
public void write(int b) throws IOException {
if (memory.hasRemaining()) {
memory.put((byte) (b & 0xff));
} else {
if (out == null) {
file = createTempFile(fileName, fileExtension, tempDirectory);
out = new FileOutputStream(file);
}
out.write(b);
}
}
@Override
public void write(byte b[], int off, int len) throws IOException {
if (out == null) {
int memLen = Math.min(memory.remaining(), len);
memory.put(b, off, memLen);
if (len > memLen) {
file = createTempFile(fileName, fileExtension, tempDirectory);
out = new FileOutputStream(file);
out.write(b, off + memLen, len - memLen);
}
} else {
out.write(b, off, len);
}
}
@Override
public void flush() throws IOException {
if (out != null) {
out.flush();
}
}
@Override
public void close() throws IOException {
if (out != null) {
out.close();
}
}
// method added just for testing purposes
File getFile() {
return file;
}
public long size() {
long size = memorySize > 0 ? memorySize : memory.position();
if (file != null) {
size += file.length();
}
return size;
}
public void clean() {
memory.clear();
memory.rewind();
if (file != null) {
file.delete();
}
}
public InputStream openWrittenDataInputStream() throws IOException {
memorySize = memory.position(); // save the memory position for size calculation as after flip() position's always 0
memory.flip();
return new ByteBufferBackedInputStream(memory, file);
}
}