
de.schlichtherle.truezip.fs.archive.tar.TarOutputShop Maven / Gradle / Ivy
/*
* Copyright (C) 2006-2011 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.truezip.fs.archive.tar;
import de.schlichtherle.truezip.io.DecoratingOutputStream;
import de.schlichtherle.truezip.entry.Entry;
import de.schlichtherle.truezip.socket.OutputSocket;
import de.schlichtherle.truezip.io.Streams;
import de.schlichtherle.truezip.fs.archive.FsMultiplexedArchiveOutputShop;
import de.schlichtherle.truezip.socket.OutputShop;
import de.schlichtherle.truezip.io.OutputBusyException;
import de.schlichtherle.truezip.socket.IOPool;
import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.tools.tar.TarOutputStream;
import static de.schlichtherle.truezip.entry.Entry.Size.DATA;
import static de.schlichtherle.truezip.entry.Entry.UNKNOWN;
/**
* An implementation of {@link OutputShop} to write TAR archives.
*
* Because the TAR file format needs to know each entry's length in advance,
* entries from an unknown source are actually written to temp files and copied
* to the underlying {@code TarOutputStream} upon a call to their
* {@link OutputStream#close} method.
* Note that this implies that the {@code close()} method may fail with
* an {@link IOException}.
*
* If the size of an entry is known in advance it's directly written to the
* underlying {@link TarOutputStream} instead.
*
* This output archive can only write one entry concurrently.
* Archive drivers may wrap this class in a {@link FsMultiplexedArchiveOutputShop}
* to overcome this limitation.
*
* @see TarInputShop
* @author Christian Schlichtherle
* @version $Id$
*/
@DefaultAnnotation(NonNull.class)
public class TarOutputShop
extends TarOutputStream
implements OutputShop {
/** Maps entry names to tar entries [String -> TarArchiveEntry]. */
private final Map entries
= new LinkedHashMap();
private final IOPool> pool;
private boolean busy;
public TarOutputShop(final TarDriver driver, OutputStream out) {
super(out);
super.setLongFileMode(LONGFILE_GNU);
this.pool = driver.getPool();
}
@Override
public int getSize() {
return entries.size();
}
@Override
public Iterator iterator() {
return entries.values().iterator();
}
@Override
public TarArchiveEntry getEntry(String name) {
return entries.get(name);
}
@Override
public OutputSocket getOutputSocket(final TarArchiveEntry entry) {
if (null == entry)
throw new NullPointerException();
class Output extends OutputSocket {
@Override
public TarArchiveEntry getLocalTarget() {
return entry;
}
@Override
public OutputStream newOutputStream() throws IOException {
if (isBusy())
throw new OutputBusyException(entry.getName());
if (entry.isDirectory()) {
entry.setSize(0);
return new EntryOutputStream(entry);
}
final Entry peer = getPeerTarget();
long size;
if (null != peer && UNKNOWN != (size = peer.getSize(DATA))) {
entry.setSize(size);
return new EntryOutputStream(entry);
}
// The source entry does not exist or cannot support DDC
// to the destination entry.
// So we need to buffer the output in a temporary file and
// write it upon close().
return new TempEntryOutputStream(
pool.allocate(),
entry);
}
} // class Output
return new Output();
}
/**
* Returns whether this output archive is busy writing an archive entry
* or not.
*/
private boolean isBusy() {
return busy;
}
/**
* This entry output stream writes directly to our subclass.
* It can only be used if this output stream is not currently busy
* writing another entry and the entry holds enough information to
* write the entry header.
* These preconditions are checked by {@link #getOutputSocket(TarArchiveEntry)}.
*/
private class EntryOutputStream extends DecoratingOutputStream {
private boolean closed;
EntryOutputStream(final TarArchiveEntry entry)
throws IOException {
super(TarOutputShop.this);
putNextEntry(entry);
entries.put(entry.getName(), entry);
busy = true;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
delegate.write(b, off, len);
}
@Override
public void close() throws IOException {
if (closed)
return;
// Order is important here!
closed = true;
busy = false;
closeEntry();
}
} // 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 this
* output stream and finally deleted.
*/
private class TempEntryOutputStream extends DecoratingOutputStream {
private final IOPool.Entry> temp;
private final TarArchiveEntry entry;
private boolean closed;
TempEntryOutputStream(final IOPool.Entry> temp, final TarArchiveEntry entry)
throws IOException {
super(temp.getOutputSocket().newOutputStream());
this.temp = temp;
this.entry = entry;
entries.put(entry.getName(), entry);
busy = true;
}
@Override
public void close() throws IOException {
if (closed)
return;
// Order is important here!
closed = true;
busy = false;
try {
super.close();
} finally {
entry.setSize(temp.getSize(DATA));
store();
}
}
void store() throws IOException {
try {
final InputStream in = temp.getInputSocket().newInputStream();
try {
putNextEntry(entry);
try {
Streams.cat(in, TarOutputShop.this);
} finally {
closeEntry();
}
} finally {
in.close();
}
} finally {
temp.release();
}
}
} // class TempEntryOutputStream
}