View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.nio.file.LinkOption;
25  import java.nio.file.Path;
26  
27  import org.apache.commons.compress.CompressFilterOutputStream;
28  
29  /**
30   * Archive output stream implementations are expected to override the {@link #write(byte[], int, int)} method to improve performance. They should also override
31   * {@link #close()} to ensure that any necessary trailers are added.
32   *
33   * <p>
34   * The normal sequence of calls when working with ArchiveOutputStreams is:
35   * </p>
36   * <ul>
37   * <li>Create ArchiveOutputStream object,</li>
38   * <li>optionally write SFX header (Zip only),</li>
39   * <li>repeat as needed:
40   * <ul>
41   * <li>{@link #putArchiveEntry(ArchiveEntry)} (writes entry header),
42   * <li>{@link #write(byte[])} (writes entry data, as often as needed),
43   * <li>{@link #closeArchiveEntry()} (closes entry),
44   * </ul>
45   * </li>
46   * <li>{@link #finish()} (ends the addition of entries),</li>
47   * <li>optionally write additional data, provided format supports it,</li>
48   * <li>{@link #close()}.</li>
49   * </ul>
50   *
51   * @param <E> The type of {@link ArchiveEntry} consumed.
52   */
53  public abstract class ArchiveOutputStream<E extends ArchiveEntry> extends CompressFilterOutputStream<OutputStream> {
54  
55      static final int BYTE_MASK = 0xFF;
56  
57      /** Temporary buffer used for the {@link #write(int)} method. */
58      private final byte[] oneByte = new byte[1];
59  
60      /** Holds the number of bytes written to this stream. */
61      private long bytesWritten;
62  
63      /**
64       * Constructs a new instance without a backing OutputStream.
65       * <p>
66       * You must initialize {@code this.out} after construction.
67       * </p>
68       */
69      public ArchiveOutputStream() {
70          // empty
71      }
72  
73      /**
74       * Constructs a new instance with the given backing OutputStream.
75       *
76       * @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
77       *            without an underlying stream.
78       * @since 1.27.0.
79       */
80      public ArchiveOutputStream(final OutputStream out) {
81          super(out);
82      }
83  
84      /**
85       * Tests whether this stream is able to write the given entry.
86       *
87       * <p>
88       * Some archive formats support variants or details that are not supported (yet).
89       * </p>
90       *
91       * @param archiveEntry the entry to test
92       * @return This implementation always returns true.
93       * @since 1.1
94       */
95      public boolean canWriteEntryData(final ArchiveEntry archiveEntry) {
96          return true;
97      }
98  
99      /**
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 }