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

de.schlichtherle.truezip.socket.IOCache 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.DecoratingEntry;
import de.schlichtherle.truezip.entry.Entry;
import de.schlichtherle.truezip.io.DecoratingInputStream;
import de.schlichtherle.truezip.io.DecoratingOutputStream;
import de.schlichtherle.truezip.io.DecoratingSeekableByteChannel;
import de.schlichtherle.truezip.rof.DecoratingReadOnlyFile;
import de.schlichtherle.truezip.rof.ReadOnlyFile;
import de.schlichtherle.truezip.util.JSE7;
import de.schlichtherle.truezip.util.Pool;
import edu.umd.cs.findbugs.annotations.CleanupObligation;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import edu.umd.cs.findbugs.annotations.DischargesObligation;
import java.io.*;
import java.nio.channels.SeekableByteChannel;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * Provides caching services for input and output sockets with the following
 * features:
 * 
    *
  • Upon the first read operation, the entry data will be read from the * backing store and stored in the cache. * Subsequent or concurrent read operations will be served from the cache * without re-reading the entry data from the backing store again until * the cache gets {@link #clear cleared}. *
  • At the discretion of the {@link Strategy}, entry data written to the * cache may not be written to the backing store until the cache gets * {@link #flush flushed}. *
  • After a write operation, the entry data will be stored in the cache * for subsequent read operations until the cache gets * {@link #clear cleared}. *
  • As a side effect, caching decouples the backing store from its * clients, allowing it to create, read, update or delete the entry data * while some clients are still busy on reading or writing the cached * entry data. *
*

* Note that you need to call `configure` before you can do any input or * output. * * @author Christian Schlichtherle */ @NotThreadSafe @CleanupObligation @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") public final class IOCache implements Flushable, Closeable { private static final SocketFactory FACTORY = JSE7.AVAILABLE ? SocketFactory.NIO2 : SocketFactory.OIO; private final Strategy strategy; private final IOPool pool; private @Nullable InputSocket input; private @Nullable OutputSocket output; private @CheckForNull InputBufferPool inputBufferPool; private @CheckForNull OutputBufferPool outputBufferPool; private @CheckForNull Buffer buffer; /** * Constructs a new cache which applies the given caching strategy, * uses the given pool to allocate and release temporary I/O entries. *

* Note that you need to call {@link #configure(InputSocket)} before * you can do any input. * Likewise, you need to call {@link #configure(OutputSocket)} before * you can do any output. * * @param strategy the caching strategy. * @param pool the pool for allocating and releasing temporary I/O entries. */ @CreatesObligation private IOCache(final Strategy strategy, final IOPool pool) { if (null == (this.strategy = strategy)) throw new NullPointerException(); if (null == (this.pool = pool)) throw new NullPointerException(); } /** * Configures the input socket for reading the entry data from the * backing store. * This method needs to be called before any input can be done - * otherwise a {@link NullPointerException} will be thrown on the first * read attempt. * Note that calling this method does not {@link #clear() clear} * this cache. * * @param input an input socket for reading the entry data from the * backing store. * @return {@code this} */ public IOCache configure(final InputSocket input) { if (null == input) throw new NullPointerException(); this.input = input; return this; } /** * Configures the output socket for writing the entry data to the * backing store. * This method needs to be called before any output can be done - * otherwise a {@link NullPointerException} will be thrown on the first * write attempt. * Note that calling this method does not {@link #flush() flush} * this cache. * * @param output an output socket for writing the entry data to the * backing store. * @return {@code this} */ public IOCache configure(final OutputSocket output) { if (null == output) throw new NullPointerException(); this.output = output; return this; } /** * Writes the cached entry data to the backing store unless already done. * Whether or not this method needs to be called depends on the caching * strategy. * E.g. the caching strategy {@link Strategy#WRITE_THROUGH} writes any * changed entry data immediately, so calling this method has no effect. */ @Override public void flush() throws IOException { final Buffer buffer = getBuffer(); if (null != buffer) getOutputBufferPool().release(buffer); } /** * Clears the entry data from this cache without flushing it. * * @throws IOException on any I/O failure. */ public void clear() throws IOException { setBuffer(null); } /** * {@linkplain #flush() Flushes} and {@linkplain #clear() clears} * the cached entry data. */ @Override @DischargesObligation public void close() throws IOException { flush(); clear(); } public @Nullable Entry getEntry() { Buffer buffer = getBuffer(); return null == buffer ? null : buffer.data; } /** * Returns an input socket for reading the cached entry data. * * @return An input socket for reading the cached entry data. */ public InputSocket getInputSocket() { return new Input(); } /** * Returns an output socket for writing the cached entry data. * * @return An output socket for writing the cached entry data. */ public OutputSocket getOutputSocket() { return new Output(); } private InputBufferPool getInputBufferPool() { final InputBufferPool ibp = inputBufferPool; return null != ibp ? ibp : (inputBufferPool = strategy.newInputBufferPool(this)); } private OutputBufferPool getOutputBufferPool() { final OutputBufferPool obp = outputBufferPool; return null != obp ? obp : (outputBufferPool = strategy.newOutputBufferPool(this)); } private @CheckForNull Buffer getBuffer() { return buffer; } private void setBuffer(final @CheckForNull Buffer newBuffer) throws IOException { final Buffer oldBuffer = this.buffer; if (oldBuffer != newBuffer) { this.buffer = newBuffer; if (null != oldBuffer && 0 == oldBuffer.writers && 0 == oldBuffer.readers) oldBuffer.release(); } } /** Provides different cache strategies. */ @Immutable @SuppressWarnings("PublicInnerClass") public enum Strategy { /** * Any attempt to obtain an output socket will result in a * {@link NullPointerException}. */ READ_ONLY { @Override IOCache.OutputBufferPool newOutputBufferPool(IOCache cache) { throw new AssertionError(); // should throw an NPE before we can get here! } }, /** * A write-through cache flushes any written data as soon as the * output stream created by {@link #getOutputSocket} gets closed. */ WRITE_THROUGH { @Override IOCache.OutputBufferPool newOutputBufferPool(IOCache cache) { return cache.new WriteThroughOutputBufferPool(); } }, /** * A write-back cache flushes any written data if and only if it gets * explicitly {@link #flush flushed}. */ WRITE_BACK { @Override IOCache.OutputBufferPool newOutputBufferPool(IOCache cache) { return cache.new WriteBackOutputBufferPool(); } }; /** * Returns a new cache. * * @param pool the pool of temporary entries to cache the entry data. * @return A new cache. */ @CreatesObligation public IOCache newCache(IOPool pool) { return new IOCache(this, pool); } IOCache.InputBufferPool newInputBufferPool(IOCache cache) { return cache.new InputBufferPool(); } abstract IOCache.OutputBufferPool newOutputBufferPool(IOCache cache); } // Strategy private final class Input extends DelegatingInputSocket { @CheckForNull Buffer buffer; @Override protected InputSocket getDelegate() throws IOException { return (buffer = getInputBufferPool().allocate()).getInputSocket(); } @Override public Entry getLocalTarget() throws IOException { final Buffer b = buffer; return null != b ? b.data : new ProxyEntry( input/*.bind(this)*/.getLocalTarget()); // do NOT bind! } } // Input private final class Output extends DelegatingOutputSocket { @CheckForNull Buffer buffer; @Override protected OutputSocket getDelegate() throws IOException { return (buffer = getOutputBufferPool().allocate()).getOutputSocket(); } @Override public Entry getLocalTarget() throws IOException { final Buffer b = buffer; return null != b ? b.data : new ProxyEntry( output/*.bind(this)*/.getLocalTarget()); // do NOT bind! } } // Output /** Used to proxy the backing store entries. */ @Immutable private static class ProxyEntry extends DecoratingEntry { ProxyEntry(Entry entry) { super(entry); } } // Proxy @Immutable private final class InputBufferPool implements Pool { @Override public Buffer allocate() throws IOException { Buffer buffer = getBuffer(); if (null == buffer) { buffer = new Buffer(); try { IOSocket.copy(input, buffer.data.getOutputSocket()); } catch (IOException ex) { buffer.release(); throw ex; } setBuffer(buffer); } assert Strategy.WRITE_BACK == strategy || 0 == buffer.writers; buffer.readers++; return buffer; } @Override public void release(final Buffer buffer) throws IOException { assert Strategy.WRITE_BACK == strategy || 0 == buffer.writers; if (0 < buffer.readers) { buffer.readers--; if (0 == buffer.readers && 0 == buffer.writers && getBuffer() != buffer) buffer.release(); } } } // InputBufferPool @Immutable private final class WriteThroughOutputBufferPool extends OutputBufferPool { @Override public void release(Buffer buffer) throws IOException { if (0 != buffer.writers) super.release(buffer); } } // WriteThroughOutputBufferPool @Immutable private final class WriteBackOutputBufferPool extends OutputBufferPool { @Override public void release(final Buffer buffer) throws IOException { if (0 != buffer.writers) { if (getBuffer() != buffer) setBuffer(buffer); else super.release(buffer); } } } // WriteBackOutputBufferPool @Immutable private abstract class OutputBufferPool implements Pool { @Override public Buffer allocate() throws IOException { final Buffer buffer = new Buffer(); assert 0 == buffer.readers; buffer.writers = 1; return buffer; } @Override public void release(final Buffer buffer) throws IOException { assert Strategy.WRITE_BACK == strategy || 0 == buffer.readers; buffer.writers = 0; try { IOSocket.copy(buffer.data.getInputSocket(), output); } finally { setBuffer(buffer); } } } // OutputBufferPool @Immutable private enum SocketFactory { NIO2() { @Override InputSocket newInputSocket(Buffer buffer) { return buffer.new Nio2Input(); } @Override OutputSocket newOutputSocket(Buffer buffer) { return buffer.new Nio2Output(); } }, OIO() { @Override InputSocket newInputSocket(Buffer buffer) { return buffer.new Input(); } @Override OutputSocket newOutputSocket(Buffer buffer) { return buffer.new Output(); } }; abstract InputSocket newInputSocket(Buffer buffer); abstract OutputSocket newOutputSocket(Buffer buffer); } // SocketFactory /** A buffer for the contents of the cache. */ private final class Buffer { final IOPool.Entry data; int readers, writers; // max one writer! Buffer() throws IOException { data = pool.allocate(); } InputSocket getInputSocket() { return FACTORY.newInputSocket(this); } OutputSocket getOutputSocket() { return FACTORY.newOutputSocket(this); } void release() throws IOException { assert 0 == writers; assert 0 == readers; data.release(); } @Immutable final class Nio2Input extends Input { @Override public SeekableByteChannel newSeekableByteChannel() throws IOException { class Channel extends DecoratingSeekableByteChannel { boolean closed; Channel() throws IOException { super(getBoundSocket().newSeekableByteChannel()); } @Override public void close() throws IOException { if (closed) return; delegate.close(); getInputBufferPool().release(Buffer.this); closed = true; } } // Channel return new Channel(); } } // Nio2Input @Immutable class Input extends DecoratingInputSocket { Input() { super(Buffer.this.data.getInputSocket()); } @Override public final ReadOnlyFile newReadOnlyFile() throws IOException { class File extends DecoratingReadOnlyFile { boolean closed; File() throws IOException { super(getBoundSocket().newReadOnlyFile()); } @Override public void close() throws IOException { if (closed) return; delegate.close(); getInputBufferPool().release(Buffer.this); closed = true; } } // File return new File(); } @Override public final InputStream newInputStream() throws IOException { class Stream extends DecoratingInputStream { boolean closed; Stream() throws IOException { super(getBoundSocket().newInputStream()); } @Override public void close() throws IOException { if (closed) return; delegate.close(); getInputBufferPool().release(Buffer.this); closed = true; } } // Stream return new Stream(); } } // Input @Immutable final class Nio2Output extends Output { @Override public SeekableByteChannel newSeekableByteChannel() throws IOException { class Channel extends DecoratingSeekableByteChannel { boolean closed; Channel() throws IOException { super(getBoundSocket().newSeekableByteChannel()); } @Override public void close() throws IOException { if (closed) return; delegate.close(); getOutputBufferPool().release(Buffer.this); closed = true; } } // Channel return new Channel(); } } // Nio2Output @Immutable class Output extends DecoratingOutputSocket { Output() { super(Buffer.this.data.getOutputSocket()); } @Override public final OutputStream newOutputStream() throws IOException { class Stream extends DecoratingOutputStream { boolean closed; Stream() throws IOException { super(getBoundSocket().newOutputStream()); } @Override public void close() throws IOException { if (closed) return; delegate.close(); getOutputBufferPool().release(Buffer.this); closed = true; } } // Stream return new Stream(); } } // Output } // Buffer }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy