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

de.schlichtherle.truezip.socket.MultiplexedOutputShop Maven / Gradle / Ivy

/*
 * Copyright (C) 2005-2015 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package de.schlichtherle.truezip.socket;

import de.schlichtherle.truezip.entry.Entry;
import static de.schlichtherle.truezip.entry.Entry.ALL_ACCESS_SET;
import de.schlichtherle.truezip.entry.Entry.Access;
import static de.schlichtherle.truezip.entry.Entry.Size.DATA;
import static de.schlichtherle.truezip.entry.Entry.UNKNOWN;
import de.schlichtherle.truezip.entry.MutableEntry;
import de.schlichtherle.truezip.io.DecoratingOutputStream;
import de.schlichtherle.truezip.io.InputException;
import de.schlichtherle.truezip.io.SequentialIOException;
import de.schlichtherle.truezip.io.SequentialIOExceptionBuilder;
import de.schlichtherle.truezip.util.JSE7;
import de.schlichtherle.truezip.util.JointIterator;
import edu.umd.cs.findbugs.annotations.CleanupObligation;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import edu.umd.cs.findbugs.annotations.DischargesObligation;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.WillCloseWhenClosed;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * Decorates annother output shop to support a virtually unlimited number of
 * entries which may be written concurrently while actually at most one entry
 * is written concurrently to the decorated output shop.
 * If there is more than one entry to be written concurrently, the additional
 * entries are buffered to an I/O entry allocated from an I/O pool and copied
 * to the decorated output shop upon a call to their
 * {@link OutputStream#close()} method.
 * Note that this implies that the {@code close()} method may fail with
 * an {@link IOException}.
 *
 * @param   the type of the archive entries.
 * @author Christian Schlichtherle
 */
@NotThreadSafe
public class MultiplexedOutputShop
extends DecoratingOutputShop> {

    private final IOPool pool;

    /**
     * The map of temporary archive entries which have not yet been written
     * to the output archive.
     */
    private final Map buffers
            = new LinkedHashMap();

    /** @see #isBusy */
    private boolean busy;

    /**
     * Constructs a new multiplexed output shop.
     * 
     * @param output the decorated output shop.
     * @param pool the pool for buffering entry data.
     */
    @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION")
    public MultiplexedOutputShop(
            final @WillCloseWhenClosed OutputShop output,
            final IOPool pool) {
        super(output);
        if (null == (this.pool = pool))
            throw new NullPointerException();
    }

    @Override
    public int getSize() {
        return delegate.getSize() + buffers.size();
    }

    @Override
    public Iterator iterator() {
        return new JointIterator(
                delegate.iterator(),
                new BufferedEntriesIterator());
    }

    private class BufferedEntriesIterator implements Iterator {
        final Iterator i = buffers.values().iterator();

        @Override
        public boolean hasNext() {
            return i.hasNext();
        }

        @Override
        public E next() {
            return i.next().getLocalTarget();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    @Override
    public @CheckForNull E getEntry(String name) {
        final E entry = delegate.getEntry(name);
        if (null != entry) return entry;
        final BufferedEntryOutputStream out = buffers.get(name);
        return null == out ? null : out.getLocalTarget();
    }

    @Override
    public OutputSocket getOutputSocket(final E local) {
        if (null == local) throw new NullPointerException();
        final class Output extends DecoratingOutputSocket {
            Output() {
                super(MultiplexedOutputShop.super.getOutputSocket(local));
            }

            @Override
            public E getLocalTarget() throws IOException {
                return local;
            }

            @Override
            @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION")
            public OutputStream newOutputStream() throws IOException {
                final OutputSocket output = getBoundSocket();
                return isBusy() ? new BufferedEntryOutputStream(output)
                                : new EntryOutputStream(output);
            }
        } // Output
        return new Output();
    }

    /**
     * Returns whether the container output archive is busy writing an archive
     * entry or not.
     * 
     * @return Whether the container output archive is busy writing an archive
     *         entry or not.
     */
    public boolean isBusy() {
        return busy;
    }

    @Override
    @DischargesObligation
    public void close() throws IOException {
        if (isBusy())
            throw new IOException("This multiplexed output shop is still busy with writing a stream!");
        storeBuffers();
        assert buffers.isEmpty();
        delegate.close();
    }

    final void storeBuffers() throws IOException {
        if (isBusy()) return;
        final SequentialIOExceptionBuilder builder
                = SequentialIOExceptionBuilder.create(IOException.class, SequentialIOException.class);
        for (   final Iterator i = buffers.values().iterator();
                i.hasNext(); ) {
            final BufferedEntryOutputStream out = i.next();
            try {
                if (out.storeBuffer()) i.remove();
            } catch (final InputException ex) {
                builder.warn(ex);
            } catch (final IOException ex) {
                throw builder.fail(ex);
            }
        }
        builder.check();
    }

    /** This entry output stream writes directly to this output shop. */
    @CleanupObligation
    private final class EntryOutputStream extends DecoratingOutputStream {
        boolean closed;

        @CreatesObligation
        @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION")
        EntryOutputStream(final OutputSocket output)
        throws IOException {
            super(output.newOutputStream());
            busy = true;
        }

        @Override
        @DischargesObligation
        public void close() throws IOException {
            if (!closed) {
                closed = true;
                busy = false;
                delegate.close();
            }
            storeBuffers();
        }
    } // EntryOutputStream

    /**
     * This entry output stream writes the archive entry to an
     * {@link de.schlichtherle.truezip.socket.IOPool.Entry I/O pool entry}.
     * When the stream gets closed, the I/O pool entry is then copied to this
     * output shop and finally deleted unless this output shop is still busy.
     */
    @CleanupObligation
    private final class BufferedEntryOutputStream
    extends DecoratingOutputStream {
        final InputSocket input;
        final OutputSocket output;
        final IOPool.Entry buffer;
        boolean closed;

        @CreatesObligation
        @SuppressWarnings("LeakingThisInConstructor")
        @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION")
        BufferedEntryOutputStream(final OutputSocket output)
        throws IOException {
            super(null);
            // FC SUNT DRACONES!
            final E local = (this.output = output).getLocalTarget();
            final Entry _peer = output.getPeerTarget();
            final IOPool.Entry buffer = this.buffer = pool.allocate();
            final Entry peer = null != _peer ? _peer : buffer;
            final class InputProxy extends DecoratingInputSocket {
                InputProxy() { super(buffer.getInputSocket()); }

                @Override
                public Entry getLocalTarget() {
                    return peer;
                }
            } // InputProxy
            try {
                this.input = new InputProxy();
                this.delegate = buffer.getOutputSocket().newOutputStream();
            } catch (final IOException ex) {
                try {
                    buffer.release();
                } catch (final IOException ex2) {
                    if (JSE7.AVAILABLE) ex.addSuppressed(ex2);
                }
                throw ex;
            }
            buffers.put(local.getName(), this);
        }

        E getLocalTarget() {
            try {
                return output.getLocalTarget();
            } catch (final IOException ex) {
                throw new AssertionError(ex);
            }
        }

        @Override
        @DischargesObligation
        public void close() throws IOException {
            final SequentialIOExceptionBuilder builder
                    = SequentialIOExceptionBuilder.create(IOException.class, SequentialIOException.class);
            if (!closed) {
                closed = true;
                try {
                    delegate.close();
                    final E local = output.getLocalTarget();
                    if (this == buffers.get(local.getName()))
                        updateProperties(local, input.getLocalTarget());
                    else
                        discardBuffer();
                } catch (final IOException ex) {
                    builder.warn(ex);
                }
            }
            try {
                storeBuffers();
            } catch (final IOException ex) {
                builder.warn(ex);
            }
            builder.check();
        }

        void updateProperties(final E local, final Entry peer) {
            for (final Access type : ALL_ACCESS_SET)
                if (UNKNOWN == local.getTime(type))
                    local.setTime(type, peer.getTime(type));
            // Never copy any but the DATA size!
            if (UNKNOWN == local.getSize(DATA))
                local.setSize(DATA, peer.getSize(DATA));
        }

        void discardBuffer() throws IOException {
            assert closed;
            buffer.release();
        }

        boolean storeBuffer() throws InputException, IOException {
            if (!closed || isBusy()) return false;
            IOSocket.copy(input, output);
            buffer.release();
            return true;
        }
    } // BufferedEntryOutputStream
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy