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}