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

org.apache.commons.compress.utils.FixedLengthBlockOutputStream Maven / Gradle / Ivy

There is a newer version: 6.5.21
Show newest version
/*
 * 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.commons.compress.utils;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * This class supports writing to an Outputstream or WritableByteChannel in fixed length blocks.
 * 

It can be be used to support output to devices such as tape drives that require output in this * format. If the final block does not have enough content to fill an entire block, the output will * be padded to a full block size.

* *

This class can be used to support TAR,PAX, and CPIO blocked output to character special devices. * It is not recommended that this class be used unless writing to such devices, as the padding * serves no useful purpose in such cases.

* *

This class should normally wrap a FileOutputStream or associated WritableByteChannel directly. * If there is an intervening filter that modified the output, such as a CompressorOutputStream, or * performs its own buffering, such as BufferedOutputStream, output to the device may * no longer be of the specified size.

* *

Any content written to this stream should be self-delimiting and should tolerate any padding * added to fill the last block.

* * @since 1.15 */ public class FixedLengthBlockOutputStream extends OutputStream implements WritableByteChannel { private final WritableByteChannel out; private final int blockSize; private final ByteBuffer buffer; private final AtomicBoolean closed = new AtomicBoolean(false); /** * Create a fixed length block output stream with given destination stream and block size * @param os The stream to wrap. * @param blockSize The block size to use. */ public FixedLengthBlockOutputStream(OutputStream os, int blockSize) { if (os instanceof FileOutputStream) { FileOutputStream fileOutputStream = (FileOutputStream) os; out = fileOutputStream.getChannel(); buffer = ByteBuffer.allocateDirect(blockSize); } else { out = new BufferAtATimeOutputChannel(os); buffer = ByteBuffer.allocate(blockSize); } this.blockSize = blockSize; } /** * Create a fixed length block output stream with given destination writable byte channel and block size * @param out The writable byte channel to wrap. * @param blockSize The block size to use. */ public FixedLengthBlockOutputStream(WritableByteChannel out, int blockSize) { this.out = out; this.blockSize = blockSize; this.buffer = ByteBuffer.allocateDirect(blockSize); } private void maybeFlush() throws IOException { if (!buffer.hasRemaining()) { writeBlock(); } } private void writeBlock() throws IOException { buffer.flip(); int i = out.write(buffer); boolean hasRemaining = buffer.hasRemaining(); if (i != blockSize || hasRemaining) { String msg = String .format("Failed to write %,d bytes atomically. Only wrote %,d", blockSize, i); throw new IOException(msg); } buffer.clear(); } @Override public void write(int b) throws IOException { if (!isOpen()) { throw new ClosedChannelException(); } buffer.put((byte) b); maybeFlush(); } @Override public void write(byte[] b, final int offset, final int length) throws IOException { if (!isOpen()) { throw new ClosedChannelException(); } int off = offset; int len = length; while (len > 0) { int n = Math.min(len, buffer.remaining()); buffer.put(b, off, n); maybeFlush(); len -= n; off += n; } } @Override public int write(ByteBuffer src) throws IOException { if (!isOpen()) { throw new ClosedChannelException(); } int srcRemaining = src.remaining(); if (srcRemaining < buffer.remaining()) { // if don't have enough bytes in src to fill up a block we must buffer buffer.put(src); } else { int srcLeft = srcRemaining; int savedLimit = src.limit(); // If we're not at the start of buffer, we have some bytes already buffered // fill up the reset of buffer and write the block. if (buffer.position() != 0) { int n = buffer.remaining(); src.limit(src.position() + n); buffer.put(src); writeBlock(); srcLeft -= n; } // whilst we have enough bytes in src for complete blocks, // write them directly from src without copying them to buffer while (srcLeft >= blockSize) { src.limit(src.position() + blockSize); out.write(src); srcLeft -= blockSize; } // copy any remaining bytes into buffer src.limit(savedLimit); buffer.put(src); } return srcRemaining; } @Override public boolean isOpen() { if (!out.isOpen()) { closed.set(true); } return !closed.get(); } /** * Potentially pads and then writes the current block to the underlying stream. * @throws IOException if writing fails */ public void flushBlock() throws IOException { if (buffer.position() != 0) { padBlock(); writeBlock(); } } @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { flushBlock(); out.close(); } } private void padBlock() { buffer.order(ByteOrder.nativeOrder()); int bytesToWrite = buffer.remaining(); if (bytesToWrite > 8) { int align = buffer.position() & 7; if (align != 0) { int limit = 8 - align; for (int i = 0; i < limit; i++) { buffer.put((byte) 0); } bytesToWrite -= limit; } while (bytesToWrite >= 8) { buffer.putLong(0L); bytesToWrite -= 8; } } while (buffer.hasRemaining()) { buffer.put((byte) 0); } } /** * Helper class to provide channel wrapper for arbitrary output stream that doesn't alter the * size of writes. We can't use Channels.newChannel, because for non FileOutputStreams, it * breaks up writes into 8KB max chunks. Since the purpose of this class is to always write * complete blocks, we need to write a simple class to take care of it. */ private static class BufferAtATimeOutputChannel implements WritableByteChannel { private final OutputStream out; private final AtomicBoolean closed = new AtomicBoolean(false); private BufferAtATimeOutputChannel(OutputStream out) { this.out = out; } @Override public int write(ByteBuffer buffer) throws IOException { if (!isOpen()) { throw new ClosedChannelException(); } if (!buffer.hasArray()) { throw new IllegalArgumentException("direct buffer somehow written to BufferAtATimeOutputChannel"); } try { int pos = buffer.position(); int len = buffer.limit() - pos; out.write(buffer.array(), buffer.arrayOffset() + pos, len); buffer.position(buffer.limit()); return len; } catch (IOException e) { try { close(); } catch (IOException ignored) { //NOSONAR } throw e; } } @Override public boolean isOpen() { return !closed.get(); } @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { out.close(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy