blob: a4101b0eef6c01a3498becf0df3bee4e41c2b48c [file] [log] [blame]
/*
* Copyright 2013-present Facebook, Inc.
*
* Licensed 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 com.facebook.buck.zip;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
/**
* An implementation of an {@link OutputStream} that will zip output. Note that, just as with
* {@link java.util.zip.ZipOutputStream}, no implementation of this is thread-safe.
*/
public abstract class CustomZipOutputStream extends OutputStream {
protected final OutputStream delegate;
private State state;
private boolean entryOpen;
protected CustomZipOutputStream(OutputStream out) {
this.delegate = Preconditions.checkNotNull(out);
this.state = State.CLEAN;
}
public final void putNextEntry(ZipEntry entry) throws IOException {
Preconditions.checkState(state != State.CLOSED, "Stream has been closed.");
Preconditions.checkNotNull(entry);
state = State.OPEN;
closeEntry();
actuallyPutNextEntry(entry);
entryOpen = true;
}
/**
* Called by {@link #putNextEntry(ZipEntry)} and used by subclasses to put the next entry into the
* zip file. It is guaranteed that the {@code entry} won't be null and the stream will be open. It
* is also guaranteed that there's no current entry open.
*
* @param entry The {@link ZipEntry} to write.
*/
protected abstract void actuallyPutNextEntry(ZipEntry entry) throws IOException;
public final void closeEntry() throws IOException {
Preconditions.checkState(state != State.CLOSED, "Stream has been closed");
if (!entryOpen) {
return; // As ZipOutputStream does.
}
entryOpen = false;
actuallyCloseEntry();
}
/**
* Called by {@link #close()} and used by subclasses to close the delegate stream. This method
* will be called at most once in the lifecycle of the CustomZipOutputStream.
*/
protected abstract void actuallyCloseEntry() throws IOException;
@Override
public final void write(byte[] b, int off, int len) throws IOException {
Preconditions.checkState(state != State.CLOSED, "Stream has been closed.");
if (!entryOpen) {
// Same exception as Java's ZipOutputStream.
throw new ZipException("no current ZIP entry");
}
actuallyWrite(b, off, len);
}
/**
* Called by {@link #write(byte[], int, int)} only once it is known that the stream has not been
* closed, and that a {@link ZipEntry} has already been put on the stream and not closed.
*/
protected abstract void actuallyWrite(byte b[], int off, int len) throws IOException;
// javadocs taken from OutputStream and amended to make it clear what we're doing here.
/**
* Writes the specified byte to this output stream. Specifically one byte is written to the
* output stream. The byte to be written is the eight low-order bits of the argument
* <code>b</code>. The 24 high-order bits of <code>b</code> are ignored.
*
* @param b the <code>byte</code>.
* @exception IOException if an I/O error occurs. In particular,
* an <code>IOException</code> may be thrown if the
* output stream has been closed.
*/
@Override
public void write(int b) throws IOException {
byte[] buf = new byte[1];
buf[0] = (byte)(b & 0xff);
write(buf, 0, 1);
}
@Override
public final void close() throws IOException {
if (state == State.CLOSED) {
return; // no-op to call close again.
}
try {
actuallyClose();
} finally {
state = State.CLOSED;
}
}
protected abstract void actuallyClose() throws IOException;
/**
* State of a {@link com.facebook.buck.zip.CustomZipOutputStream}. Certain operations are only
* available when the stream is in a particular state.
*/
private static enum State {
CLEAN, // Open but no data written.
OPEN, // Open and data written.
CLOSED, // Just as it says on the tin.
}
}