
de.schlichtherle.io.archive.spi.MultiplexedOutputArchive Maven / Gradle / Ivy
/*
* Copyright (C) 2006-2010 Schlichtherle IT Services
*
* 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 de.schlichtherle.io.archive.spi;
import de.schlichtherle.io.ChainableIOException;
import de.schlichtherle.io.File;
import de.schlichtherle.io.InputIOException;
import de.schlichtherle.io.OutputArchiveMetaData;
import de.schlichtherle.io.archive.tar.TarEntry;
import de.schlichtherle.io.archive.zip.ZipEntry;
import de.schlichtherle.io.util.Temps;
import de.schlichtherle.util.JointEnumeration;
import java.io.FileNotFoundException;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A decorator for output archives which allows to write an unlimited number
* of entries concurrently while actually only one entry is written at a time
* to the target output archive.
* If there is more than one entry to be written concurrently, the additional
* entries are actually written to temp files and copied to the target
* output archive upon a call to their {@link OutputStream#close} method.
* Note that this implies that the {@code close()} method may fail with
* an {@link IOException}.
*
* @author Christian Schlichtherle
* @version $Id: MultiplexedOutputArchive.java,v 1.5 2010/10/10 16:31:20 christian_schlichtherle Exp $
* @since TrueZIP 6.5
*/
public class MultiplexedOutputArchive implements OutputArchive {
/** Prefix for temporary files created by the multiplexer. */
static final String TEMP_FILE_PREFIX = "tzp-mux";
/** The decorated output archive. */
private final OutputArchive target;
/**
* The map of temporary archive entries which have not yet been written
* to the target output archive.
* Maps from entry names [{@link String}] to temporary entry output
* streams [{@link TempEntryOutputStream}].
*/
private final Map temps = new LinkedHashMap();
/** @see #isTargetBusy */
private boolean targetBusy;
/**
* Constructs a new {@code MultiplexedOutputArchive}.
*
* @param target The decorated output archive.
* @throws NullPointerException Iff {@code target} is {@code null}.
*/
public MultiplexedOutputArchive(final OutputArchive target) {
if (target == null)
throw new NullPointerException();
this.target = target;
}
public int getNumArchiveEntries() {
return target.getNumArchiveEntries() + temps.size();
}
public Enumeration getArchiveEntries() {
return new JointEnumeration(target.getArchiveEntries(),
new TempEntriesEnumeration());
}
private class TempEntriesEnumeration implements Enumeration {
private final Iterator i = temps.values().iterator();
public boolean hasMoreElements() {
return i.hasNext();
}
public Object nextElement() {
return ((TempEntryOutputStream) i.next()).entry;
}
}
public ArchiveEntry getArchiveEntry(String entryName) {
ArchiveEntry entry = target.getArchiveEntry(entryName);
if (entry != null)
return entry;
final TempEntryOutputStream tempOut
= (TempEntryOutputStream) temps.get(entryName);
return tempOut != null ? tempOut.entry : null;
}
public OutputStream getOutputStream(
final ArchiveEntry entry,
final ArchiveEntry srcEntry)
throws IOException {
if (srcEntry != null)
setSize(entry, srcEntry.getSize()); // data may be compressed!
if (isTargetBusy()) {
final java.io.File temp = Temps.createTempFile(TEMP_FILE_PREFIX);
return new TempEntryOutputStream(entry, srcEntry, temp);
}
return new EntryOutputStream(entry, srcEntry);
}
/**
* Returns whether the target output archive is busy writing an archive
* entry or not.
*/
public boolean isTargetBusy() {
return targetBusy;
}
/**
* This entry output stream writes directly to the target output archive.
*/
private class EntryOutputStream extends FilterOutputStream {
private boolean closed;
private EntryOutputStream(
final ArchiveEntry entry,
final ArchiveEntry srcEntry)
throws IOException {
super(target.getOutputStream(entry, srcEntry));
targetBusy = true;
}
public void write(byte[] b) throws IOException {
out.write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
public void close() throws IOException {
if (closed)
return;
// Order is important here!
closed = true;
targetBusy = false;
super.close();
storeTempEntries();
}
} // class EntryOutputStream
/**
* This entry output stream writes the entry to a temporary file.
* When the stream is closed, the temporary file is then copied to the
* target output archive and finally deleted unless the target is still
* busy.
*/
private class TempEntryOutputStream extends java.io.FileOutputStream {
private final ArchiveEntry entry, srcEntry;
private final java.io.File temp;
private boolean closed;
private TempEntryOutputStream(
final ArchiveEntry entry,
final ArchiveEntry srcEntry,
final java.io.File temp)
throws IOException {
super(temp);
this.entry = entry;
this.srcEntry = srcEntry != null ? srcEntry : new RfsEntry(temp);
this.temp = temp;
temps.put(entry.getName(), this);
}
public void close() throws IOException {
if (closed)
return;
// Order is important here!
closed = true;
super.close();
if (entry.getSize() == ArchiveEntry.UNKNOWN)
setSize(entry, temp.length());
if (entry.getTime() == ArchiveEntry.UNKNOWN)
entry.setTime(temp.lastModified());
storeTempEntries();
}
} // class TempEntryOutputStream
// TODO: Add setSize(long) to ArchiveEntry interface and remove this method!
private void setSize(final ArchiveEntry entry, final long size) {
if (entry instanceof ZipEntry) {
((ZipEntry) entry).setSize(size);
} else if (entry instanceof TarEntry) {
((TarEntry) entry).setSize(size);
} else {
assert false : "Unknown archive entry type: File.length() may return 0 while the temp file hasn't yet been saved to the output archive.";
}
}
private void storeTempEntries() throws IOException {
if (isTargetBusy())
return;
ChainableIOException exception = null;
for (final Iterator i = temps.values().iterator(); i.hasNext(); ) {
final TempEntryOutputStream tempOut
= (TempEntryOutputStream) i.next();
if (!tempOut.closed)
continue;
try {
final ArchiveEntry entry = tempOut.entry;
final ArchiveEntry srcEntry = tempOut.srcEntry;
final java.io.File temp = tempOut.temp;
try {
final InputStream in = new java.io.FileInputStream(temp);
try {
final OutputStream out = target.getOutputStream(
entry, srcEntry);
try {
File.cat(in, out);
} finally {
out.close();
}
} finally {
in.close();
}
} finally {
if (!temp.delete()) // may fail on Windoze if in.close() failed!
temp.deleteOnExit(); // we're bullish never to leavy any temps!
}
} catch (FileNotFoundException ex) {
// Input exception - let's continue!
exception = new ChainableIOException(exception, ex);
} catch (InputIOException ex) {
// Input exception - let's continue!
exception = new ChainableIOException(exception, ex);
} catch (IOException ex) {
// Something's wrong writing this MultiplexedOutputStream!
throw new ChainableIOException(exception, ex);
} finally {
i.remove();
}
}
if (exception != null)
throw exception.sortPriority();
}
/**
* @deprecated This method will be removed in the next major version number
* release and should be implemented as
* {@code getOutputStream(entry, null).close()}.
*/
public final void storeDirectory(ArchiveEntry entry) throws IOException {
assert false : "Since TrueZIP 6.5, this is not used anymore!";
if (!entry.isDirectory())
throw new IllegalArgumentException();
getOutputStream(entry, null).close();
}
public void close() throws IOException {
assert !isTargetBusy();
try {
storeTempEntries();
assert temps.isEmpty();
} finally {
target.close();
}
}
public OutputArchiveMetaData getMetaData() {
return target.getMetaData();
}
public void setMetaData(OutputArchiveMetaData metaData) {
target.setMetaData(metaData);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy