    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress;
    020
    021import java.io.File;
    022import java.io.FilterOutputStream;
    023import java.io.IOException;
    024import java.io.OutputStream;
    025import java.nio.charset.Charset;
    026import java.nio.charset.StandardCharsets;
    027import java.nio.file.Files;
    028import java.nio.file.Path;
    029import java.util.concurrent.atomic.AtomicBoolean;
    030
    031/**
    032 * Abstracts classes that compress or archive an output stream.
    033 *
    034 * @param <T> The underlying {@link OutputStream} type.
    035 * @since 1.28.0
    036 */
    037public abstract class CompressFilterOutputStream<T extends OutputStream> extends FilterOutputStream {
    038
    039    /**
    040     * Writes and filters the bytes from the specified String to this output stream using the given Charset.
    041     *
    042     * @param os      the target output stream.
    043     * @param data    the data.
    044     * @param charset The {@link Charset} to be used to encode the {@code String}
    045     * @return the ASCII bytes.
    046     * @exception IOException if an I/O error occurs.
    047     * @see OutputStream#write(byte[])
    048     */
    049    private static byte[] write(final OutputStream os, final String data, final Charset charset) throws IOException {
    050        final byte[] bytes = data.getBytes(charset);
    051        os.write(bytes);
    052        return bytes;
    053    }
    054
    055    /**
    056     * Whether this instance was successfully closed.
    057     */
    058    private final AtomicBoolean closed = new AtomicBoolean();
    059    /**
    060     * Whether this instance was successfully finished.
    061     * <p>
    062     * The state transition usually is open, to finished, to closed.
    063     * </p>
    064     */
    065    private boolean finished;
    066
    067    /**
    068     * Constructs a new instance without a backing {@link OutputStream}.
    069     * <p>
    070     * You must initialize {@code this.out} after construction.
    071     * </p>
    072     */
    073    public CompressFilterOutputStream() {
    074        super(null);
    075    }
    076
    077    /**
    078     * Creates an output stream filter built on top of the specified underlying {@link OutputStream}.
    079     *
    080     * @param out the underlying output stream to be assigned to the field {@code this.out} for later use, or {@code null} if this instance is to be created
    081     *            without an underlying stream.
    082     */
    083    public CompressFilterOutputStream(final T out) {
    084        super(out);
    085    }
    086
    087    /**
    088     * Check to make sure that this stream has not been closed.
    089     *
    090     * @throws IOException if the stream is already closed.
    091     */
    092    protected void checkOpen() throws IOException {
    093        if (isClosed()) {
    094            throw new IOException("Stream closed");
    095        }
    096    }
    097
    098    @Override
    099    public void close() throws IOException {
    100        if (closed.compareAndSet(false, true)) {
    101            // don't propagate more than once.
    102            super.close();
    103        }
    104    }
    105
    106    /**
    107     * Finishes the addition of entries to this stream, without closing it. Additional data can be written, if the format supports it.
    108     *
    109     * @throws IOException Maybe thrown by subclasses if the user forgets to close the entry.
    110     */
    111    public void finish() throws IOException {
    112        finished = true;
    113    }
    114
    115    /**
    116     * Tests whether this instance was successfully closed.
    117     *
    118     * @return whether this instance was successfully closed.
    119     * @since 1.27.0
    120     */
    121    public boolean isClosed() {
    122        return closed.get();
    123    }
    124
    125    /**
    126     * Tests whether this instance was successfully finished.
    127     *
    128     * @return whether this instance was successfully finished.
    129     * @since 1.27.0
    130     */
    131    protected boolean isFinished() {
    132        return finished;
    133    }
    134
    135    /**
    136     * Gets the underlying output stream.
    137     *
    138     * @return the underlying output stream.
    139     */
    140    @SuppressWarnings("unchecked")
    141    protected T out() {
    142        return (T) out;
    143    }
    144
    145    /**
    146     * Writes all bytes from a file this output stream.
    147     *
    148     * @param file the path to the source file.
    149     * @return the number of bytes read or written.
    150     * @throws IOException if an I/O error occurs when reading or writing.
    151     */
    152    public long write(final File file) throws IOException {
    153        return write(file.toPath());
    154    }
    155
    156    /**
    157     * Writes all bytes from a file to this output stream.
    158     *
    159     * @param path the path to the source file.
    160     * @return the number of bytes read or written.
    161     * @throws IOException if an I/O error occurs when reading or writing.
    162     */
    163    public long write(final Path path) throws IOException {
    164        return Files.copy(path, this);
    165    }
    166
    167    /**
    168     * Writes and filters the ASCII bytes from the specified String to this output stream.
    169     *
    170     * @param data the data.
    171     * @return the ASCII bytes.
    172     * @throws IOException if an I/O error occurs.
    173     * @see OutputStream#write(byte[])
    174     */
    175    public byte[] writeUsAscii(final String data) throws IOException {
    176        return write(this, data, StandardCharsets.US_ASCII);
    177    }
    178
    179    /**
    180     * Writes the raw ASCII bytes from the specified String to this output stream.
    181     *
    182     * @param data the data.
    183     * @return the ASCII bytes.
    184     * @throws IOException if an I/O error occurs.
    185     * @see OutputStream#write(byte[])
    186     */
    187    public byte[] writeUsAsciiRaw(final String data) throws IOException {
    188        return write(out, data, StandardCharsets.US_ASCII);
    189    }
    190
    191    /**
    192     * Writes and filters the UTF-8 bytes from the specified String to this output stream.
    193     *
    194     * @param data the data.
    195     * @return the ASCII bytes.
    196     * @throws IOException if an I/O error occurs.
    197     * @see OutputStream#write(byte[])
    198     */
    199    public byte[] writeUtf8(final String data) throws IOException {
    200        return write(this, data, StandardCharsets.UTF_8);
    201    }
    202}