/*
* Copyright 2017-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.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import java.io.IOException;
import java.io.OutputStream;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import javax.annotation.Nullable;
/** Extension of {@link CustomZipOutputStream} with jar-specific functionality. */
public class CustomJarOutputStream extends CustomZipOutputStream {
public static final String DIGEST_ATTRIBUTE_NAME = "Murmur3-128-Digest";
private final HashingImpl impl;
public CustomJarOutputStream(Impl impl) {
this(new HashingImpl(impl));
}
private CustomJarOutputStream(HashingImpl impl) {
super(impl);
this.impl = impl;
}
public DeterministicManifest getManifest() {
return impl.getManifest();
}
public void setEntryHashingEnabled(boolean shouldHashEntries) {
impl.setEntryHashingEnabled(shouldHashEntries);
}
public void writeManifest() throws IOException {
impl.writeManifest();
}
private static class HashingImpl extends OutputStream implements Impl {
private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128();
private final Impl inner;
private final DeterministicManifest manifest = new DeterministicManifest();
private boolean shouldHashEntries = false;
private boolean manifestWritten = false;
@Nullable private ZipEntry currentEntry;
@Nullable private Hasher hasher;
HashingImpl(Impl inner) {
this.inner = inner;
}
public DeterministicManifest getManifest() {
return manifest;
}
public void setEntryHashingEnabled(boolean shouldHashEntries) {
this.shouldHashEntries = shouldHashEntries;
}
@Override
public void actuallyPutNextEntry(ZipEntry entry) throws IOException {
inner.actuallyPutNextEntry(entry);
currentEntry = entry;
}
@Override
public void actuallyWrite(byte[] b, int off, int len) throws IOException {
inner.actuallyWrite(b, off, len);
if (shouldHashEntries && hasher == null) {
hasher = HASH_FUNCTION.newHasher();
}
if (hasher != null) {
hasher.putBytes(b, off, len);
}
}
@Override
public void actuallyCloseEntry() throws IOException {
inner.actuallyCloseEntry();
if (hasher != null) {
if (manifestWritten) {
throw new IllegalStateException(
"Attempted to write an entry with hashing enabled after the manifest was written.");
}
manifest.setEntryAttribute(
currentEntry.getName(), DIGEST_ATTRIBUTE_NAME, hasher.hash().toString());
hasher = null;
}
currentEntry = null;
}
@Override
public void actuallyClose() throws IOException {
shouldHashEntries = false;
writeManifest();
inner.actuallyClose();
}
protected void writeManifest() throws IOException {
if (shouldHashEntries || manifestWritten) {
return;
}
inner.actuallyPutNextEntry(new CustomZipEntry(JarFile.MANIFEST_NAME));
try {
manifest.write(this);
} finally {
inner.actuallyCloseEntry();
}
manifestWritten = true;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
actuallyWrite(b, off, len);
}
@Override
public void write(int b) throws IOException {
throw new UnsupportedOperationException();
}
}
}