| /* |
| * 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. |
| } |
| } |