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

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 06f3ba684701 2010/11/04 01:15:55 christian $
 * @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