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

org.xnio.channels.FixedLengthStreamSourceChannel Maven / Gradle / Ivy

There is a newer version: 3.8.16.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 *
 * Copyright 2012 Red Hat, Inc. and/or its affiliates, and individual
 * contributors as indicated by the @author tags.
 *
 * 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 org.xnio.channels;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import org.xnio.ChannelListener;
import org.xnio.ChannelListeners;
import org.xnio.Option;
import org.xnio.XnioExecutor;
import org.xnio.XnioWorker;

import static java.lang.Math.min;
import static org.xnio.Bits.*;

/**
 * A channel which reads data of a fixed length and calls a finish listener.  When the finish listener is called,
 * it should examine the result of {@link #getRemaining()} to see if more bytes were pending when the channel was
 * closed.
 *
 * @author David M. Lloyd
 */
/*
 * Implementation notes
 * --------------------
 * The {@code exhausted} flag is set once a method returns -1 and signifies that the read listener should no longer be
 * called.  The {@code finishListener} is called when remaining is reduced to 0 or when the channel is closed explicitly.
 * If there are 0 remaining bytes but {@code FLAG_FINISHED} has not yet been set, the channel is considered "ready" until
 * the EOF -1 value is read or the channel is closed.  Since this is a half-duplex channel, shutting down reads is
 * identical to closing the channel.
 */
public final class FixedLengthStreamSourceChannel implements StreamSourceChannel, ProtectedWrappedChannel {
    private final StreamSourceChannel delegate;
    private final boolean configurable;
    private final Object guard;

    private final ChannelListener finishListener;
    private final ChannelListener.SimpleSetter readSetter = new ChannelListener.SimpleSetter();
    private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter();

    @SuppressWarnings("unused")
    private volatile long state;

    private static final long FLAG_READ_ENTERED = 1L << 63L;
    private static final long FLAG_CLOSED = 1L << 62L;
    private static final long FLAG_SUS_RES_SHUT = 1L << 61L;
    private static final long FLAG_FINISHED = 1L << 60L;
    private static final long MASK_COUNT = longBitMask(0, 59);

    private static final AtomicLongFieldUpdater stateUpdater = AtomicLongFieldUpdater.newUpdater(FixedLengthStreamSourceChannel.class, "state");

    /**
     * Construct a new instance.  The given listener is called once all the bytes are read from the stream
     * or the stream is closed.  This listener should cause the remaining data to be drained from the
     * underlying stream via the {@link #drain()} method if the underlying stream is to be reused.
     * 

* Calling this constructor will replace the read listener of the underlying channel. The listener should be * restored from the {@code finishListener} object. The underlying stream should not be closed while this wrapper * stream is active. * * @param delegate the stream source channel to read from * @param contentLength the amount of content to read * @param finishListener the listener to call once the stream is exhausted or closed * @param guard the guard object to use */ public FixedLengthStreamSourceChannel(final StreamSourceChannel delegate, final long contentLength, final ChannelListener finishListener, final Object guard) { this(delegate, contentLength, false, finishListener, guard); } /** * Construct a new instance. The given listener is called once all the bytes are read from the stream * or the stream is closed. This listener should cause the remaining data to be drained from the * underlying stream via the {@link #drain()} method if the underlying stream is to be reused. *

* Calling this constructor will replace the read listener of the underlying channel. The listener should be * restored from the {@code finishListener} object. The underlying stream should not be closed while this wrapper * stream is active. * * @param delegate the stream source channel to read from * @param contentLength the amount of content to read * @param configurable {@code true} to allow options to pass through to the delegate, {@code false} otherwise * @param finishListener the listener to call once the stream is exhausted or closed * @param guard the guard object to use */ public FixedLengthStreamSourceChannel(final StreamSourceChannel delegate, final long contentLength, final boolean configurable, final ChannelListener finishListener, final Object guard) { this.guard = guard; this.finishListener = finishListener; if (contentLength < 0L) { throw new IllegalArgumentException("Content length must be greater than or equal to zero"); } else if (contentLength > MASK_COUNT) { throw new IllegalArgumentException("Content length is too long"); } this.delegate = delegate; stateUpdater.lazySet(this, contentLength); delegate.getReadSetter().set(ChannelListeners.delegatingChannelListener(FixedLengthStreamSourceChannel.this, readSetter)); this.configurable = configurable; } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { long val = enterRead(); if (anyAreSet(val, FLAG_CLOSED | FLAG_FINISHED) || allAreClear(val, MASK_COUNT)) { return 0L; } long res = 0L; try { return res = delegate.transferTo(position, min(count, val), target); } finally { exitRead(val, res); } } public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { if (count == 0L) { return 0L; } long val = enterRead(); if (anyAreSet(val, FLAG_CLOSED | FLAG_FINISHED) || allAreClear(val, MASK_COUNT)) { return -1; } long res = 0L; try { if (allAreSet(val, FLAG_CLOSED) || val == 0L) { return -1L; } return res = delegate.transferTo(min(count, val), throughBuffer, target); } finally { exitRead(val, res == -1L ? val & MASK_COUNT : res); } } public ChannelListener.Setter getReadSetter() { return readSetter; } public ChannelListener.Setter getCloseSetter() { return closeSetter; } public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { if (length == 0) { return 0L; } else if (length == 1) { return read(dsts[offset]); } long val = enterRead(); if (allAreSet(val, FLAG_CLOSED) || allAreClear(val, MASK_COUNT)) { return -1; } long res = 0L; try { if ((val & MASK_COUNT) == 0L) { return -1L; } int lim; // The total amount of buffer space discovered so far. long t = 0L; for (int i = 0; i < length; i ++) { final ByteBuffer buffer = dsts[i + offset]; // Grow the discovered buffer space by the remaining size of the current buffer. // We want to capture the limit so we calculate "remaining" ourselves. t += (lim = buffer.limit()) - buffer.position(); if (t > (val & MASK_COUNT)) { // only read up to this point, and trim the last buffer by the number of extra bytes buffer.limit(lim - (int) (t - (val & MASK_COUNT))); try { return res = delegate.read(dsts, offset, i + 1); } finally { // restore the original limit buffer.limit(lim); } } } // the total buffer space is less than the remaining count. return res = delegate.read(dsts, offset, length); } finally { exitRead(val, res == -1L ? val & MASK_COUNT : res); } } public long read(final ByteBuffer[] dsts) throws IOException { return read(dsts, 0, dsts.length); } public int read(final ByteBuffer dst) throws IOException { long val = enterRead(); if (allAreSet(val, FLAG_CLOSED) || allAreClear(val, MASK_COUNT)) { return -1; } int res = 0; final long remaining = val & MASK_COUNT; try { final int lim = dst.limit(); final int pos = dst.position(); if (lim - pos > remaining) { dst.limit((int) (remaining - (long) pos)); try { return res = delegate.read(dst); } finally { dst.limit(lim); } } else { return res = delegate.read(dst); } } finally { exitRead(val, res == -1 ? remaining : (long) res); } } public void suspendReads() { long val = enterSuspendResume(); if (anyAreSet(val, FLAG_CLOSED | FLAG_SUS_RES_SHUT | FLAG_FINISHED) || allAreClear(val, MASK_COUNT)) { return; } try { delegate.suspendReads(); } finally { exitSuspendResume(val); } } public void resumeReads() { long val = enterSuspendResume(); if (anyAreSet(val, FLAG_CLOSED | FLAG_SUS_RES_SHUT | FLAG_FINISHED) || allAreClear(val, MASK_COUNT)) { return; } try { if (val == 0L) { delegate.wakeupReads(); } else { delegate.resumeReads(); } } finally { exitSuspendResume(val); } } public boolean isReadResumed() { return allAreClear(state, FLAG_CLOSED) && delegate.isReadResumed(); } public void wakeupReads() { long val = enterSuspendResume(); if (anyAreSet(val, FLAG_CLOSED | FLAG_SUS_RES_SHUT | FLAG_FINISHED) || allAreClear(val, MASK_COUNT)) { return; } try { delegate.wakeupReads(); } finally { exitSuspendResume(val); } } public void shutdownReads() throws IOException { long val = enterShutdownReads(); if (allAreSet(val, FLAG_CLOSED)) { return; } try { if (false) { // propagate close if configured to do so delegate.shutdownReads(); } } finally { // listener(s) called from here exitShutdownReads(val); } } public void awaitReadable() throws IOException { final long val = state; if (allAreSet(val, FLAG_CLOSED) || val == 0L) { return; } delegate.awaitReadable(); } public void awaitReadable(final long time, final TimeUnit timeUnit) throws IOException { final long val = state; if (allAreSet(val, FLAG_CLOSED) || val == 0L) { return; } delegate.awaitReadable(time, timeUnit); } public XnioExecutor getReadThread() { return delegate.getReadThread(); } public XnioWorker getWorker() { return delegate.getWorker(); } public boolean isOpen() { return allAreClear(state, FLAG_CLOSED); } public void close() throws IOException { shutdownReads(); } public boolean supportsOption(final Option option) { return configurable && delegate.supportsOption(option); } public T getOption(final Option option) throws IOException { return configurable ? delegate.getOption(option) : null; } public T setOption(final Option option, final T value) throws IllegalArgumentException, IOException { return configurable ? delegate.setOption(option, value) : null; } public StreamSourceChannel getChannel(final Object guard) { final Object ourGuard = this.guard; if (ourGuard == null || guard == ourGuard) { return delegate; } else { return null; } } /** * Get the number of remaining bytes. * * @return the number of remaining bytes */ public long getRemaining() { return state & MASK_COUNT; } private long enterShutdownReads() { long oldVal, newVal; do { oldVal = state; if (anyAreSet(oldVal, FLAG_CLOSED)) { return oldVal; } newVal = oldVal | FLAG_CLOSED | FLAG_SUS_RES_SHUT; } while (! stateUpdater.weakCompareAndSet(this, oldVal, newVal)); return oldVal; } private void exitShutdownReads(long oldVal) { final boolean wasFinished = allAreClear(oldVal, MASK_COUNT); final boolean wasInSusRes = allAreSet(oldVal, FLAG_SUS_RES_SHUT); final boolean wasEntered = allAreSet(oldVal, FLAG_READ_ENTERED); if (! wasInSusRes) { long newVal = oldVal & ~FLAG_SUS_RES_SHUT; while (! stateUpdater.compareAndSet(this, oldVal, newVal)) { oldVal = state; newVal = oldVal & ~FLAG_SUS_RES_SHUT; } if (! wasEntered) { if (! wasFinished && allAreClear(newVal, MASK_COUNT)) { callFinish(); } callClosed(); } } // else let exitSuspendResume/exitReads handle this } private long enterSuspendResume() { long oldVal, newVal; do { oldVal = state; if (anyAreSet(oldVal, FLAG_CLOSED | FLAG_SUS_RES_SHUT)) { return oldVal; } newVal = oldVal | FLAG_SUS_RES_SHUT; } while (! stateUpdater.weakCompareAndSet(this, oldVal, newVal)); return oldVal; } private void exitSuspendResume(long oldVal) { final boolean wasFinished = allAreClear(oldVal, MASK_COUNT); final boolean wasClosed = allAreClear(oldVal, FLAG_CLOSED); final boolean wasEntered = allAreSet(oldVal, FLAG_READ_ENTERED); long newVal = oldVal & ~FLAG_SUS_RES_SHUT; while (! stateUpdater.compareAndSet(this, oldVal, newVal)) { oldVal = state; newVal = oldVal & ~FLAG_SUS_RES_SHUT; } if (! wasEntered) { if (! wasFinished && allAreClear(newVal, MASK_COUNT)) { callFinish(); } if (! wasClosed && allAreSet(newVal, FLAG_CLOSED)) { callClosed(); } } } /** * Enter the method. Does not set entered flag if the channel is closed so * the caller must return immediately in this case. * * @return the original state */ private long enterRead() { long oldVal, newVal; do { oldVal = state; if (allAreSet(oldVal, FLAG_CLOSED) || allAreClear(oldVal, MASK_COUNT)) { // do not swap return oldVal; } if (allAreSet(oldVal, FLAG_READ_ENTERED)) { throw new ConcurrentStreamChannelAccessException(); } newVal = oldVal | FLAG_READ_ENTERED; } while (! stateUpdater.weakCompareAndSet(this, oldVal, newVal)); return oldVal; } /** * Exit a read method. * * @param oldVal the original state * @param consumed the number of bytes consumed by this call (may be 0) */ private void exitRead(long oldVal, long consumed) { long newVal = oldVal - consumed; while (! stateUpdater.compareAndSet(this, oldVal, newVal)) { oldVal = state; newVal = oldVal & ~FLAG_READ_ENTERED - consumed; } if (allAreSet(newVal, FLAG_CLOSED)) { // closed while we were in flight. Call the listener. callClosed(); } if (anyAreSet(oldVal, MASK_COUNT) && allAreClear(newVal, MASK_COUNT)) { callFinish(); } } private void callFinish() { ChannelListeners.invokeChannelListener(this, finishListener); } private void callClosed() { ChannelListeners.invokeChannelListener(this, closeSetter.get()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy