/* * Copyright [2013] [Cloud4SOA, www.cloud4soa.eu] * * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ /* * Copyright 2009-2012 the original author or authors. * * 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 org.cloudfoundry.client.lib.io; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.springframework.util.Assert; /** * InputStream that dynamically creates ZIP contents as the stream is read without consuming too much memory. Zip * {@link Entry entries} should be provided on {@link #DynamicZipInputStream(Iterable) construction}. * * @author Phillip Webb */ public class DynamicZipInputStream extends DynamicInputStream { private static final int BUFFER_SIZE = 4096; private static InputStream EMPTY_STREAM = new InputStream() { @Override public int read() throws IOException { return -1; } }; /** * The underlying ZIP stream. */ private ZipOutputStream zipStream; /** * Entries to be written. */ private Iterator<Entry> entries; /** * The current entry {@link InputStream}. */ private InputStream entryStream = EMPTY_STREAM; /** * Buffer for reading stream contents. */ private byte[] buffer = new byte[BUFFER_SIZE]; /** * File counter used for detecting empty archives. */ private long fileCount = 0; /** * Create a new {@link DynamicZipInputStream} instance. * * @param entries the zip entries that should be written to the stream */ public DynamicZipInputStream(Iterable<Entry> entries) { Assert.notNull(entries, "Entries must not be null"); this.zipStream = new ZipOutputStream(getOutputStream()); this.entries = entries.iterator(); } @Override protected boolean writeMoreData() throws IOException { // Write data from the current stream if possible int count = entryStream.read(buffer); if (count != -1) { zipStream.write(buffer, 0, count); return true; } // Close any open entry if (entryStream != EMPTY_STREAM) { zipStream.closeEntry(); entryStream.close(); entryStream = EMPTY_STREAM; } // Move to the next entry if there is one (no need to write data as returning true causes another call) if (entries.hasNext()) { fileCount++; Entry entry = entries.next(); zipStream.putNextEntry(new ZipEntry(entry.getName())); entryStream = entry.getInputStream(); if(entryStream == null) { entryStream = EMPTY_STREAM; } return true; } // If no files were added to the archive add an empty one if (fileCount == 0) { fileCount++; zipStream.putNextEntry(new ZipEntry("__empty__")); entryStream = EMPTY_STREAM; return true; } // No more entries, close and flush the stream zipStream.flush(); zipStream.close(); return false; } @Override public void close() throws IOException { super.close(); zipStream.close(); } /** * Represents a single entry from a ZIP files. */ public static interface Entry { /** * Returns the name of the entry complete with path, equivalent to {@link ZipEntry#getName()}. * * @return the name of the entry */ String getName(); /** * Opens a new stream that can be used to read the contents of the entry. The steam will be closed by the * caller. * * @return the entry input stream * @throws IOException */ InputStream getInputStream() throws IOException; } }