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

com.mycila.hc.io.InputStreamContentProvider.groovy Maven / Gradle / Ivy

/**
 * Copyright (C) 2013 Mycila ([email protected])
 *
 * 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 com.mycila.hc.io

import java.nio.ByteBuffer

/**
 * A {@link ContentProvider} for an {@link InputStream}.
 * 

* The input stream is read once and therefore fully consumed. * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator * because the stream has been consumed on the first invocation. *

* However, it is possible for subclasses to override {@link #onRead(byte [ ], int, int)} to copy * the content read from the stream to another location (for example a file), and be able to * support multiple invocations of {@link #iterator()}, returning the iterator provided by this * class on the first invocation, and an iterator on the bytes copied to the other location * for subsequent invocations. *

* It is possible to specify, at the constructor, a buffer size used to read content from the * stream, by default 4096 bytes. *

* The {@link InputStream} passed to the constructor is by default closed when is it fully * consumed (or when an exception is thrown while reading it), unless otherwise specified * to the {@link #InputStreamContentProvider(java.io.InputStream, int, boolean) constructor}. */ class InputStreamContentProvider implements ContentProvider { protected static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]) protected final InputStream stream protected final int bufferSize protected final boolean autoClose InputStreamContentProvider(InputStream stream, int bufferSize, boolean autoClose = true) { this.stream = stream this.bufferSize = bufferSize this.autoClose = autoClose } @Override void close() throws IOException { stream.close() } @Override public long getLength() { return -1 } @Override public Iterator iterator() { return new InputStreamIterator() } /** * Iterating over an {@link InputStream} is tricky, because {@link #hasNext()} must return false * if the stream reads -1. However, we don't know what to return until we read the stream, which * means that stream reading must be performed by {@link #hasNext()}, which introduces a side-effect * on what is supposed to be a simple query method (with respect to the Query Command Separation * Principle). *

* Alternatively, we could return {@code true} from {@link #hasNext()} even if we don't know that * we will read -1, but then when {@link #next()} reads -1 it must return an empty buffer. * However this is problematic, since GETs with no content indication would become GET with chunked * content, and not understood by servers. *

* Therefore we need to make sure that {@link #hasNext()} does not perform any side effect (so that * it can be called multiple times) until {@link #next()} is called. */ protected class InputStreamIterator implements Iterator, Closeable { protected Throwable failure protected ByteBuffer buffer protected Boolean hasNext @Override public boolean hasNext() { try { if (hasNext != null) return hasNext byte[] bytes = new byte[bufferSize] int read = stream.read(bytes) if (read > 0) { hasNext = Boolean.TRUE buffer = onRead(bytes, 0, read) return true } else if (read < 0) { hasNext = Boolean.FALSE buffer = null close() return false } else { hasNext = Boolean.TRUE buffer = EMPTY_BUFFER return true } } catch (Throwable x) { if (failure == null) { failure = x // Signal we have more content to cause a call to // next() which will throw NoSuchElementException. hasNext = Boolean.TRUE buffer = null close() return true } throw new IllegalStateException() } } @Override public ByteBuffer next() { if (failure != null) { // Consume the failure so that calls to hasNext() will return false. hasNext = Boolean.FALSE buffer = null throw (NoSuchElementException) new NoSuchElementException().initCause(failure) } if (!hasNext()) throw new NoSuchElementException() ByteBuffer result = buffer if (result == null) { hasNext = Boolean.FALSE buffer = null throw new NoSuchElementException() } else { hasNext = null buffer = null return result } } @Override public void remove() { throw new UnsupportedOperationException() } @Override public void close() { if (autoClose) { try { stream.close() } catch (IOException ignored) { } } } } /** * Callback method invoked just after having read from the stream, * but before returning the iteration element (a {@link ByteBuffer} * to the caller. *

* Subclasses may override this method to copy the content read from * the stream to another location (a file, or in memory if the content * is known to fit). * * @param buffer the byte array containing the bytes read * @param offset the offset from where bytes should be read * @param length the length of the bytes read * @return a {@link ByteBuffer} wrapping the byte array */ protected static ByteBuffer onRead(byte[] buffer, int offset, int length) { if (length <= 0) return EMPTY_BUFFER return ByteBuffer.wrap(buffer, offset, length) } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy