    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.archivers;
    020
    021import java.io.File;
    022import java.io.IOException;
    023import java.io.OutputStream;
    024import java.nio.file.LinkOption;
    025import java.nio.file.Path;
    026
    027import org.apache.commons.compress.CompressFilterOutputStream;
    028
    029/**
    030 * Archive output stream implementations are expected to override the {@link #write(byte[], int, int)} method to improve performance. They should also override
    031 * {@link #close()} to ensure that any necessary trailers are added.
    032 *
    033 * <p>
    034 * The normal sequence of calls when working with ArchiveOutputStreams is:
    035 * </p>
    036 * <ul>
    037 * <li>Create ArchiveOutputStream object,</li>
    038 * <li>optionally write SFX header (Zip only),</li>
    039 * <li>repeat as needed:
    040 * <ul>
    041 * <li>{@link #putArchiveEntry(ArchiveEntry)} (writes entry header),
    042 * <li>{@link #write(byte[])} (writes entry data, as often as needed),
    043 * <li>{@link #closeArchiveEntry()} (closes entry),
    044 * </ul>
    045 * </li>
    046 * <li>{@link #finish()} (ends the addition of entries),</li>
    047 * <li>optionally write additional data, provided format supports it,</li>
    048 * <li>{@link #close()}.</li>
    049 * </ul>
    050 *
    051 * @param <E> The type of {@link ArchiveEntry} consumed.
    052 */
    053public abstract class ArchiveOutputStream<E extends ArchiveEntry> extends CompressFilterOutputStream<OutputStream> {
    054
    055    static final int BYTE_MASK = 0xFF;
    056
    057    /** Temporary buffer used for the {@link #write(int)} method. */
    058    private final byte[] oneByte = new byte[1];
    059
    060    /** Holds the number of bytes written to this stream. */
    061    private long bytesWritten;
    062
    063    /**
    064     * Constructs a new instance without a backing OutputStream.
    065     * <p>
    066     * You must initialize {@code this.out} after construction.
    067     * </p>
    068     */
    069    public ArchiveOutputStream() {
    070        // empty
    071    }
    072
    073    /**
    074     * Constructs a new instance with the given backing OutputStream.
    075     *
    076     * @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
    077     *            without an underlying stream.
    078     * @since 1.27.0.
    079     */
    080    public ArchiveOutputStream(final OutputStream out) {
    081        super(out);
    082    }
    083
    084    /**
    085     * Tests whether this stream is able to write the given entry.
    086     *
    087     * <p>
    088     * Some archive formats support variants or details that are not supported (yet).
    089     * </p>
    090     *
    091     * @param archiveEntry the entry to test
    092     * @return This implementation always returns true.
    093     * @since 1.1
    094     */
    095    public boolean canWriteEntryData(final ArchiveEntry archiveEntry) {
    096        return true;
    097    }
    098
    099    /**
    100     * Throws an {@link IOException} if this instance is already finished.
    101     *
    102     * @throws IOException if this instance is already finished.
    103     * @since 1.27.0
    104     */
    105    protected void checkFinished() throws IOException {
    106        if (isFinished()) {
    107            throw new IOException("Stream has already been finished.");
    108        }
    109    }
    110
    111    /**
    112     * Closes the archive entry, writing any trailer information that may be required.
    113     *
    114     * @throws IOException if an I/O error occurs
    115     */
    116    public abstract void closeArchiveEntry() throws IOException;
    117
    118    /**
    119     * Increments the counter of already written bytes. Doesn't increment if EOF has been hit ({@code written == -1}).
    120     *
    121     * @param written the number of bytes written
    122     */
    123    protected void count(final int written) {
    124        count((long) written);
    125    }
    126
    127    /**
    128     * Increments the counter of already written bytes. Doesn't increment if EOF has been hit ({@code written == -1}).
    129     *
    130     * @param written the number of bytes written
    131     * @since 1.1
    132     */
    133    protected void count(final long written) {
    134        if (written != -1) {
    135            bytesWritten += written;
    136        }
    137    }
    138
    139    /**
    140     * Creates an archive entry using the inputFile and entryName provided.
    141     *
    142     * @param inputFile the file to create the entry from
    143     * @param entryName name to use for the entry
    144     * @return the ArchiveEntry set up with details from the file
    145     * @throws IOException if an I/O error occurs
    146     */
    147    public abstract E createArchiveEntry(File inputFile, String entryName) throws IOException;
    148
    149    /**
    150     * Creates an archive entry using the inputPath and entryName provided.
    151     * <p>
    152     * The default implementation calls simply delegates as:
    153     * </p>
    154     *
    155     * <pre>
    156     * return createArchiveEntry(inputFile.toFile(), entryName);
    157     * </pre>
    158     * <p>
    159     * Subclasses should override this method.
    160     * </p>
    161     *
    162     * @param inputPath the file to create the entry from
    163     * @param entryName name to use for the entry
    164     * @param options   options indicating how symbolic links are handled.
    165     * @return the ArchiveEntry set up with details from the file
    166     * @throws IOException if an I/O error occurs
    167     * @since 1.21
    168     */
    169    public E createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
    170        return createArchiveEntry(inputPath.toFile(), entryName);
    171    }
    172
    173    /**
    174     * Gets the current number of bytes written to this stream.
    175     *
    176     * @return the number of written bytes
    177     * @since 1.1
    178     */
    179    public long getBytesWritten() {
    180        return bytesWritten;
    181    }
    182
    183    /**
    184     * Gets the current number of bytes written to this stream.
    185     *
    186     * @return the number of written bytes
    187     * @deprecated this method may yield wrong results for large archives, use #getBytesWritten instead
    188     */
    189    @Deprecated
    190    public int getCount() {
    191        return (int) bytesWritten;
    192    }
    193
    194    /**
    195     * Writes the headers for an archive entry to the output stream. The caller must then write the content to the stream and call {@link #closeArchiveEntry()}
    196     * to complete the process.
    197     *
    198     * @param entry describes the entry
    199     * @throws IOException if an I/O error occurs
    200     */
    201    public abstract void putArchiveEntry(E entry) throws IOException;
    202
    203    /**
    204     * Writes a byte to the current archive entry.
    205     *
    206     * <p>
    207     * This method simply calls {@code write( byte[], 0, 1 )}.
    208     *
    209     * <p>
    210     * MUST be overridden if the {@link #write(byte[], int, int)} method is not overridden; may be overridden otherwise.
    211     *
    212     * @param b The byte to be written.
    213     * @throws IOException on error
    214     */
    215    @Override
    216    public void write(final int b) throws IOException {
    217        oneByte[0] = (byte) (b & BYTE_MASK);
    218        write(oneByte, 0, 1);
    219    }
    220}