All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavemaker.commons.zip.DynamicZipInputStream Maven / Gradle / Ivy

There is a newer version: 11.9.4
Show newest version
/**
 * Copyright (C) 2020 WaveMaker, 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.wavemaker.commons.zip; import java.io.ByteArrayOutputStream; 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 InputStream { private static final int BUFFER_SIZE = 4096; private static final 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 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; private BufferedOutputStream outputStream = new BufferedOutputStream(); /** * Create a new {@link DynamicZipInputStream} instance. * * @param entries * the zip entries that should be written to the stream */ public DynamicZipInputStream(Iterable entries) { Assert.notNull(entries, "Entries must not be null"); this.zipStream = new ZipOutputStream(outputStream); this.entries = entries.iterator(); } private byte[] singleByte = new byte[1]; @Override public int read() throws IOException { int s = read(singleByte); if (s == 1) { return (int) singleByte[0] & 0xFF; } return -1; } @Override public int read(byte[] b, int off, int len) throws IOException { if (off < 0 || len < 0 || len > (b.length - off)) { throw new IndexOutOfBoundsException(); } if (len == 0) { return 0; } return doRead(b, off, len, true); } private int doRead(byte[] b, int off, int len, boolean lastWriteWasSuccessful) throws IOException { if (outputStream.getAvailable() > 0) { return outputStream.read(b, off, len); } if (!lastWriteWasSuccessful) { return -1; } outputStream.clear(); boolean writeSuccess = writeMoreData(); return doRead(b, off, len, writeSuccess); } 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; } private static class BufferedOutputStream extends ByteArrayOutputStream { private int offset; public int getAvailable() { return count - offset; } public void clear() { this.count = 0; this.offset = 0; } public int read(byte[] b, int off, int len) { int length = Math.min(getAvailable(), len); Assert.state(length > 0, "No data available in buffer"); System.arraycopy(this.buf, this.offset, b, off, length); offset += length; return length; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy