OK, we can keep the class but I don't fully understand the difference so
maybe I need to re-read the code or the docs can be clearer.

We should make it non-public to reduce the API surface and avoid possible
confusion.

Ty!
Gary



On Thu, Jul 24, 2025, 04:34 Jochen Wiedmann <jochen.wiedm...@gmail.com>
wrote:

> Hi, Gary,
>
> sorry for the late reply. I missed your question initially.
>
>
>
>
> On Sun, Jul 13, 2025 at 4:17 PM Gary Gregory <garydgreg...@gmail.com>
> wrote:
>
> > Why duplicate Commons IO's DeferredFileOutputStream? It seems better
> > to reuse than to duplicate or reinvent the wheel.
>
> Of course, adapting the DFO from commons-io (DFOCI) was my first idea.
> But, while implementing
> a unit test, for the DiskFileItem, based on that, a few things became
> clear to me:
>
> - The dependency on DFOCI is exactly, what caused the bug report
> (FILEUPLOAD-295), because
>   the former changed it's behaviour.
> - Even worse, by adapting DFOCI to suit the needs of FileUpload, I'd
> be most likely change it's
>   behaviour again. (Which would probably trigger a revert, hence yet
> another change.)
> - Having a dedicated implementation (DeferrableOutputStream, DFOCF) in
> our own area
>   with a unit test, that maintains the expected behaviour, is the best
> way to ensure compliance
>   to a specified behaviour. And, obviously, such compliance is expected.
>
> I have tried to point that out in the Javadocs (probably not very
> well). If you're not happy
> with my approach: Feel free to revert my changes. But, if so, you
> should also take up
> FILEUPLOAD-295, because (according to the reporters feedback), that
> can be resolved
> now.
>
> Jochen
>
> P.S: Public class, or not, I don't care.
>
>
> >
> > If the new class is required, can Commons IO be improved instead to
> > meet FileUpload's needs?
> >
> > If not, does the new class need to be public?
> >
> > TY!
> > Gary
> >
> > On Sat, Jul 12, 2025 at 4:03 PM <joc...@apache.org> wrote:
> > >
> > > This is an automated email from the ASF dual-hosted git repository.
> > >
> > > jochen pushed a commit to branch master
> > > in repository
> https://gitbox.apache.org/repos/asf/commons-fileupload.git
> > >
> > >
> > > The following commit(s) were added to refs/heads/master by this push:
> > >      new c24e594b FILEUPLOAD-295: Implementation of the
> DeferrableOutputStream
> > > c24e594b is described below
> > >
> > > commit c24e594b4973a7fad75ca320d37f97b7682ef043
> > > Author: Jochen Wiedmann <jochen.wiedm...@gmail.com>
> > > AuthorDate: Sun Jul 13 01:03:30 2025 +0200
> > >
> > >     FILEUPLOAD-295: Implementation of the DeferrableOutputStream
> > > ---
> > >  .../fileupload2/core/DeferrableOutputStream.java   | 369
> +++++++++++++++++++++
> > >  .../core/DeferrableOutputStreamTest.java           | 235 +++++++++++++
> > >  2 files changed, 604 insertions(+)
> > >
> > > diff --git
> a/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/DeferrableOutputStream.java
> b/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/DeferrableOutputStream.java
> > > new file mode 100644
> > > index 00000000..6f9c57f1
> > > --- /dev/null
> > > +++
> b/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/DeferrableOutputStream.java
> > > @@ -0,0 +1,369 @@
> > > +/*
> > > + * Licensed to the Apache Software Foundation (ASF) under one or more
> > > + * contributor license agreements.  See the NOTICE file distributed
> with
> > > + * this work for additional information regarding copyright ownership.
> > > + * The ASF licenses this file to You 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.apache.commons.fileupload2.core;
> > > +
> > > +import java.io.ByteArrayInputStream;
> > > +import java.io.ByteArrayOutputStream;
> > > +import java.io.IOException;
> > > +import java.io.InputStream;
> > > +import java.io.OutputStream;
> > > +import java.nio.file.Files;
> > > +import java.nio.file.Path;
> > > +import java.util.function.Supplier;
> > > +
> > > +
> > > +/** An {@link OutputStream}, which keeps its data in memory, until a
> configured
> > > + * threshold is reached. If that is the case, a temporary file is
> being created,
> > > + * and the in-memory data is transferred to that file. All following
> data will
> > > + * be written to that file, too.
> > > + *
> > > + * In other words: If an uploaded file is small, then it will be kept
> completely
> > > + * in memory. On the other hand, if the uploaded file's size exceeds
> the
> > > + * configured threshold, it it considered a large file, and the data
> is kept
> > > + * in a temporary file.
> > > + *
> > > + * More precisely, this output stream supports three modes of
> operation:
> > > + * <ol>
> > > + *   <li>{@code threshold=-1}: <em>Always</em> create a temporary
> file, even if
> > > + *     the uploaded file is empty.</li>
> > > + *   <li>{@code threshold=0}: Don't create empty, temporary files.
> (Create a
> > > + *     temporary file, as soon as the first byte is written.)</li>
> > > + *   <li>{@code threshold>0}: Create a temporary file, if the size
> exceeds the
> > > + *     threshold, otherwise keep the file in memory.</li>
> > > + * </ol>
> > > + *
> > > + * Technically, this is similar to
> > > + * {@link org.apache.commons.io.output.DeferredFileOutputStream},
> which has
> > > + * been used in the past, except that this implementation observes
> > > + * a precisely specified behaviour, and semantics, that match the
> needs of the
> > > + * {@link DiskFileItem}.
> > > + *
> > > + * Background: Over the various versions of commons-io, the
> > > + * {@link org.apache.commons.io.output.DeferredFileOutputStream} has
> changed
> > > + * semantics, and behaviour more than once.
> > > + * (For details, see
> > > + * <a href="https://issues.apache.org/jira/browse/FILEUPLOAD-295
> ">FILEUPLOAD-295</a>)
> > > + */
> > > +public class DeferrableOutputStream extends OutputStream {
> > > +    /** This enumeration represents the possible states of the {@link
> DeferrableOutputStream}.
> > > +     */
> > > +    public enum State {
> > > +        /** The stream object has been created with a non-negative
> threshold,
> > > +         * but so far no data has been written.
> > > +         */
> > > +        initialized,
> > > +        /** The stream object has been created with a non-negative
> threshold,
> > > +         * and some data has been written, but the threshold is not
> yet exceeded,
> > > +         * and the data is still kept in memory.
> > > +         */
> > > +        opened,
> > > +        /** Either of the following conditions is given:
> > > +         * <ol>
> > > +         *   <li>The stream object has been created with a threshold
> of -1, or</li>
> > > +         *   <li>the stream object has been created with a
> non-negative threshold,
> > > +         *     and some data has been written. The number of bytes,
> that have
> > > +         *     been written, exceeds the configured threshold.</li>
> > > +         * </ol>
> > > +         * In either case, a temporary file has been created, and all
> data has been
> > > +         * written to the temporary file, erasing all existing data
> from memory.
> > > +         */
> > > +        persisted,
> > > +        /** The stream has been closed, and data can no longer be
> written. It is
> > > +         * now valid to invoke {@link
> DeferrableOutputStream#getInputStream()}.
> > > +         */
> > > +        closed
> > > +    }
> > > +    /** The configured threshold, as an integer. This variable isn't
> actually
> > > +     * used. Instead {@link #longThreshold} is used.
> > > +     * @see #longThreshold
> > > +     */
> > > +    private final int threshold;
> > > +    /** The configured threshold, as a long integer. (Using a long
> integer
> > > +     * enables proper handling of the threshold, when the file size is
> > > +     * approaching {@link Integer#MAX_VALUE}.
> > > +     * @see #threshold
> > > +     */
> > > +    private final long longThreshold;
> > > +    /** This supplier will be invoked, if the temporary file is
> created,
> > > +     * to determine the temporary file's location.
> > > +     * @see #path
> > > +     */
> > > +    private final Supplier<Path> pathSupplier;
> > > +    /** If a temporary file has been created: Path of the temporary
> > > +     * file. Otherwise null.
> > > +     * @see #pathSupplier
> > > +     */
> > > +    private Path path;
> > > +    /** If no temporary file was created: A stream, to which the
> > > +     * incoming data is being written, until the threshold is reached.
> > > +     * Otherwise null.
> > > +     */
> > > +    private ByteArrayOutputStream baos;
> > > +    /** If no temporary file was created, and the stream is closed:
> > > +     * The in-memory data, that was written to the stream. Otherwise
> null.
> > > +     */
> > > +    private byte[] bytes;
> > > +    /** If a temporary file has been created: An open stream
> > > +     * for writing to that file. Otherwise null.
> > > +     */
> > > +    private OutputStream out;
> > > +    /** The streams current state.
> > > +     */
> > > +    private State state;
> > > +    /** True, if the stream has ever been in state {@link
> State#persisted}.
> > > +     * Or, in other words: True, if a temporary file has been created.
> > > +     */
> > > +    private boolean wasPersisted;
> > > +
> > > +    /** Creates a new instance with the given threshold, and the
> given supplier for a
> > > +     * temporary files path.
> > > +     * If the threshold is -1, then the temporary file will be
> created immediately, and
> > > +     * no in-memory data will be kept, at all.
> > > +     * If the threshold is 0, then the temporary file will be
> created, as soon as the
> > > +     * first byte will be written, but no in-memory data will be kept.
> > > +     * If the threshold is >0, then the temporary file will be
> created, as soon as that
> > > +     * number of bytes have been written. Up to that point, data will
> be kept in an
> > > +     * in-memory buffer.
> > > +     *
> > > +     * @param threshold Either of -1 (Create the temporary file
> immediately), 0 (Create
> > > +     *   the temporary file, as soon as data is being written for the
> first time), or >0
> > > +     *   (Keep data in memory, as long as the given number of bytes
> is reached, then
> > > +     *   create a temporary file, and continue using that).
> > > +     * @param pathSupplier A supplier for the temporary files path.
> This supplier must
> > > +     *   not return null. The file's directory will be created, if
> necessary, by
> > > +     *   invoking {@link Files#createDirectories(Path,
> java.nio.file.attribute.FileAttribute...)}.
> > > +     * @throws IOException Creating the temporary file (in the case
> of threshold -1)
> > > +     *   has failed.
> > > +     */
> > > +    public DeferrableOutputStream(final int threshold, final
> Supplier<Path> pathSupplier) throws IOException {
> > > +        if (threshold < 0) {
> > > +            this.threshold = -1;
> > > +        } else {
> > > +            this.threshold = threshold;
> > > +        }
> > > +        longThreshold = (long) threshold;
> > > +        this.pathSupplier = pathSupplier;
> > > +        checkThreshold(0);
> > > +    }
> > > +
> > > +    /** Returns the streams configured threshold.
> > > +     * @return The streams configured threshold.
> > > +     */
> > > +    public int getThreshold() {
> > > +        return threshold;
> > > +    }
> > > +
> > > +    /** Called to check, whether the threshold will be exceeded, if
> the given number
> > > +     * of bytes are written to the stream. If so, persists the
> in-memory data by
> > > +     * creating a new, temporary file, and writing the in-memory data
> to the file.
> > > +     * @param numberOfIncomingBytes The number of bytes, which are
> about to be written.
> > > +     * @return The actual output stream, to which the incoming data
> may be written.
> > > +     *   If the threshold is not yet exceeded, then this will be an
> internal
> > > +     *   {@link ByteArrayOutputStream}, otherwise a stream, which is
> writing to the
> > > +     *   temporary output file.
> > > +     * @throws IOException Persisting the in-memory data to a
> temporary file
> > > +     *   has failed.
> > > +     */
> > > +    protected OutputStream checkThreshold(final int
> numberOfIncomingBytes) throws IOException {
> > > +        if (state == null) {
> > > +            // Called from the constructor, state is unspecified.
> > > +            if (threshold == -1) {
> > > +                return persist();
> > > +            } else {
> > > +                baos = new ByteArrayOutputStream();
> > > +                bytes = null;
> > > +                state = State.initialized;
> > > +                return baos;
> > > +            }
> > > +        } else {
> > > +            switch (state) {
> > > +            case initialized:
> > > +            case opened:
> > > +                final int bytesWritten = baos.size();
> > > +                if ((long) bytesWritten + (long)
> numberOfIncomingBytes >= longThreshold) {
> > > +                    return persist();
> > > +                }
> > > +                if (numberOfIncomingBytes > 0) {
> > > +                    state = State.opened;
> > > +                }
> > > +                return baos;
> > > +            case persisted:
> > > +                // Do nothing, we're staying in the current state.
> > > +                return out;
> > > +            case closed:
> > > +                // Do nothing, we're staying in the current state.
> > > +                return null;
> > > +            default:
> > > +                throw illegalStateError();
> > > +            }
> > > +        }
> > > +    }
> > > +
> > > +    /** Create the output file, change the state to {@code
> persisted}, and
> > > +     * return an {@link OutputStream}, which is writing to that file.
> > > +     * @return The {@link OutputStream}, which is writing to the
> created,
> > > +     * temporary file.
> > > +     * @throws IOException Creating the temporary file has failed.
> > > +     */
> > > +    protected OutputStream persist() throws IOException {
> > > +        final Path p = pathSupplier.get();
> > > +        final Path dir = p.getParent();
> > > +        if (dir != null) {
> > > +            Files.createDirectories(dir);
> > > +        }
> > > +        final OutputStream os = Files.newOutputStream(p);
> > > +        if (baos != null) {
> > > +            baos.writeTo(os);
> > > +        }
> > > +        /** At this point, the output file has been successfully
> created,
> > > +         * and we can safely switch state.
> > > +         */
> > > +        state = State.persisted;
> > > +        wasPersisted = true;
> > > +        path = p;
> > > +        out = os;
> > > +        baos = null;
> > > +        bytes = null;
> > > +        return os;
> > > +    }
> > > +
> > > +    @Override
> > > +    public void write(final int b) throws IOException {
> > > +        final OutputStream os = checkThreshold(1);
> > > +        if (os == null) {
> > > +            throw new IOException("This stream has already been
> closed.");
> > > +        }
> > > +        bytes = null;
> > > +        os.write(b);
> > > +    }
> > > +
> > > +    @Override
> > > +    public void write(final byte[] buffer) throws IOException {
> > > +        write(buffer, 0, buffer.length);
> > > +    }
> > > +
> > > +    @Override
> > > +    public void write(final byte[] buffer, final int offset, final
> int len) throws IOException {
> > > +        if (len > 0) {
> > > +            final OutputStream os = checkThreshold(len);
> > > +            if (os == null) {
> > > +                throw new IOException("This stream has already been
> closed.");
> > > +            }
> > > +            bytes = null;
> > > +            os.write(buffer, offset, len);
> > > +        }
> > > +    }
> > > +
> > > +    @Override
> > > +    public void close() throws IOException {
> > > +        switch (state) {
> > > +        case initialized:
> > > +        case opened:
> > > +            bytes = baos.toByteArray();
> > > +            baos = null;
> > > +            state = State.closed;
> > > +            break;
> > > +        case persisted:
> > > +            bytes = null;
> > > +            out.close();
> > > +            state = State.closed;
> > > +            break;
> > > +        case closed:
> > > +            // Already closed, do nothing.
> > > +            break;
> > > +        default:
> > > +            throw illegalStateError();
> > > +        }
> > > +    }
> > > +
> > > +    /** Returns true, if this stream was never persisted,
> > > +     * and no output file has been created.
> > > +     * @return True, if the stream was never in state
> > > +     *   {@link State#persisted}, otherwise false.
> > > +     */
> > > +    public boolean isInMemory() {
> > > +        switch (state) {
> > > +        case initialized:
> > > +        case opened:
> > > +            return true;
> > > +        case persisted:
> > > +            return false;
> > > +        case closed:
> > > +            return !wasPersisted;
> > > +        default:
> > > +            throw illegalStateError();
> > > +        }
> > > +    }
> > > +
> > > +    /** Returns the streams current state.
> > > +     * @return The streams current state.
> > > +     */
> > > +    public State getState() {
> > > +        return state;
> > > +    }
> > > +
> > > +    /** Returns the output file, that has been created, if any, or
> null.
> > > +     * The latter is the case, if {@link #isInMemory()} returns true.
> > > +     * @return The output file, that has been created, if any, or
> null.
> > > +     */
> > > +    public Path getPath() {
> > > +        return path;
> > > +    }
> > > +
> > > +    /** Returns the data, that has been written, if the stream has
> > > +     * been closed, and the stream is still in memory
> > > +     * ({@link #isInMemory()} returns true). Otherwise, returns null.
> > > +     * @return If the stream is closed (no more data can be written),
> > > +     *   and the data is still in memory (no temporary file has been
> > > +     *   created), returns the data, that has been written. Otherwise,
> > > +     *   returns null.
> > > +     */
> > > +    public byte[] getBytes() {
> > > +        return bytes;
> > > +    }
> > > +
> > > +    /** If the stream is closed: Returns an {@link InputStream} on the
> > > +     * data, that has been written to this stream. Otherwise, throws
> > > +     * an {@link IllegalStateException}.
> > > +     * @return An {@link InputStream} on the data, that has been
> > > +     * written. Never null.
> > > +     * @throws IllegalStateException The stream has not yet been
> > > +     *   closed.
> > > +     * @throws IOException Creating the {@link InputStream} has
> > > +     *   failed.
> > > +     */
> > > +    public InputStream getInputStream() throws IOException {
> > > +        if (state == State.closed) {
> > > +            if (bytes != null) {
> > > +                return new ByteArrayInputStream(bytes);
> > > +            } else {
> > > +                return Files.newInputStream(path);
> > > +            }
> > > +        } else {
> > > +            throw new IllegalStateException("This stream isn't yet
> closed.");
> > > +        }
> > > +    }
> > > +
> > > +    /** Returns the path of the output file, if such a file has
> > > +     * been created. That is the case, if {@link #isInMemory()}
> > > +     * returns false. Otherwise, returns null.
> > > +     * @return Path of the created output file, if any, or null.
> > > +     */
> > > +    private IllegalStateException illegalStateError() {
> > > +        throw new IllegalStateException("Expected state
> initialized|opened|persisted|closed, got " + state.name());
> > > +    }
> > > +}
> > > diff --git
> a/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/DeferrableOutputStreamTest.java
> b/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/DeferrableOutputStreamTest.java
> > > new file mode 100644
> > > index 00000000..60a0e83e
> > > --- /dev/null
> > > +++
> b/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/DeferrableOutputStreamTest.java
> > > @@ -0,0 +1,235 @@
> > > +/*
> > > + * Licensed to the Apache Software Foundation (ASF) under one or more
> > > + * contributor license agreements.  See the NOTICE file distributed
> with
> > > + * this work for additional information regarding copyright ownership.
> > > + * The ASF licenses this file to You 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.apache.commons.fileupload2.core;
> > > +
> > > +import static org.junit.jupiter.api.Assertions.*;
> > > +
> > > +import java.io.ByteArrayOutputStream;
> > > +import java.io.IOException;
> > > +import java.io.InputStream;
> > > +import java.io.OutputStream;
> > > +import java.io.UncheckedIOException;
> > > +import java.nio.charset.StandardCharsets;
> > > +import java.nio.file.Files;
> > > +import java.nio.file.Path;
> > > +import java.nio.file.Paths;
> > > +import java.util.function.Consumer;
> > > +import java.util.function.Supplier;
> > > +
> > > +import org.junit.jupiter.api.BeforeAll;
> > > +import org.junit.jupiter.api.Test;
> > > +
> > > +import
> org.apache.commons.fileupload2.core.DeferrableOutputStream.State;
> > > +
> > > +
> > > +/** Test suite for the {@link DeferrableOutputStream}.
> > > + */
> > > +class DeferrableOutputStreamTest {
> > > +       private static final Path testDir =
> Paths.get("target/unit-tests/DeferrableOutputStreamTest");
> > > +       private static Path tempTestDir;
> > > +       private Supplier<Path> testFileSupplier = () -> {
> > > +               try {
> > > +                       return Files.createTempFile(tempTestDir,
> "testFile", ".bin");
> > > +               } catch (IOException ioe) {
> > > +                       throw new UncheckedIOException(ioe);
> > > +               }
> > > +       };
> > > +
> > > +       @BeforeAll
> > > +       static void setUpTestDirs() throws IOException {
> > > +               Files.createDirectories(testDir);
> > > +               tempTestDir = Files.createTempDirectory(testDir,
> "testDir");
> > > +       }
> > > +
> > > +       /** Tests using the {@link DeferrableOutputStream} with a
> positive threshold.
> > > +        */
> > > +       @Test
> > > +       void testExceedPositiveThreshold() {
> > > +               DeferrableOutputStream[] streams = new
> DeferrableOutputStream[1];
> > > +               final Consumer<Consumer<OutputStream>> tester =
> (consumer) -> {
> > > +                       try (final DeferrableOutputStream dos = new
> DeferrableOutputStream(5, testFileSupplier)) {
> > > +                               streams[0] = dos;
> > > +                               assertTrue(dos.isInMemory());
> > > +                               assertNull(dos.getPath());
> > > +                               assertNull(dos.getBytes());
> > > +                               assertSame(State.initialized,
> dos.getState());
> > > +                               for (int i = 0;  i < 4;  i++) {
> > > +                                       try {
> > > +                                               dos.write('.');
> > > +                                       } catch (IOException ioe) {
> > > +                                               throw new
> UncheckedIOException(ioe);
> > > +                                       }
> > > +                                       assertSame(State.opened,
> dos.getState());
> > > +                                       assertTrue(dos.isInMemory());
> > > +                                       assertNull(dos.getPath());
> > > +                                       assertNull(dos.getBytes());
> > > +                               }
> > > +                               consumer.accept(dos);
> > > +                               assertFalse(dos.isInMemory());
> > > +                               assertNotNull(dos.getPath());
> > > +                               assertNull(dos.getBytes());
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +
> > > +                       final DeferrableOutputStream dos = streams[0];
> > > +                       assertFalse(dos.isInMemory());
> > > +                       assertNotNull(dos.getPath());
> > > +                       assertTrue(Files.isRegularFile(dos.getPath()));
> > > +                       final byte[] actual;
> > > +                       try (InputStream is = dos.getInputStream()) {
> > > +                               actual = read(is);
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +                       final byte[] expect =
> "....,".getBytes(StandardCharsets.UTF_8);
> > > +                       assertArrayEquals(expect, actual);
> > > +               };
> > > +
> > > +               // Break the threshold using OutputStream.write(int);
> > > +               tester.accept((os) -> {
> > > +                       try {
> > > +                               os.write(',');
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +               });
> > > +               // Break the threshold using
> OutputStream.write(byte[]);
> > > +               tester.accept((os) -> {
> > > +                       final byte[] buffer = new byte[] {','};
> > > +                       try {
> > > +                               os.write(buffer);
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +               });
> > > +               // Break the threshold using
> OutputStream.write(byte[], int, int);
> > > +               tester.accept((os) -> {
> > > +                       final byte[] buffer = new byte[] {',', '-'};
> > > +                       try {
> > > +                               os.write(buffer, 0, 1);
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +               });
> > > +       }
> > > +
> > > +       /** Tests using the {@link DeferrableOutputStream} with
> threshold 0.
> > > +        */
> > > +       @Test
> > > +       void testThresholdZero() {
> > > +               DeferrableOutputStream[] streams = new
> DeferrableOutputStream[1];
> > > +               final Consumer<Consumer<OutputStream>> tester =
> (consumer) -> {
> > > +                       try (final DeferrableOutputStream dos = new
> DeferrableOutputStream(0, testFileSupplier)) {
> > > +                               streams[0] = dos;
> > > +                               assertTrue(dos.isInMemory());
> > > +                               assertNull(dos.getPath());
> > > +                               assertNull(dos.getBytes());
> > > +                               assertSame(State.initialized,
> dos.getState());
> > > +                               consumer.accept(dos);
> > > +                               assertFalse(dos.isInMemory());
> > > +                               assertNotNull(dos.getPath());
> > > +                               assertNull(dos.getBytes());
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +
> > > +                       final DeferrableOutputStream dos = streams[0];
> > > +                       assertFalse(dos.isInMemory());
> > > +                       assertNotNull(dos.getPath());
> > > +                       assertTrue(Files.isRegularFile(dos.getPath()));
> > > +                       final byte[] actual;
> > > +                       try (InputStream is = dos.getInputStream()) {
> > > +                               actual = read(is);
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +                       final byte[] expect =
> ",".getBytes(StandardCharsets.UTF_8);
> > > +                       assertArrayEquals(expect, actual);
> > > +               };
> > > +               // Break the threshold using OutputStream.write(int);
> > > +               tester.accept((os) -> {
> > > +                       try {
> > > +                               os.write(',');
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +               });
> > > +               // Break the threshold using
> OutputStream.write(byte[]);
> > > +               tester.accept((os) -> {
> > > +                       final byte[] buffer = new byte[] {','};
> > > +                       try {
> > > +                               os.write(buffer);
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +               });
> > > +               // Break the threshold using
> OutputStream.write(byte[], int, int);
> > > +               tester.accept((os) -> {
> > > +                       final byte[] buffer = new byte[] {',', '-'};
> > > +                       try {
> > > +                               os.write(buffer, 0, 1);
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +               });
> > > +       }
> > > +
> > > +       /** Tests using the {@link DeferrableOutputStream} with
> threshold -1.
> > > +        */
> > > +       @Test
> > > +       void testThresholdMinusOne() {
> > > +               DeferrableOutputStream[] streams = new
> DeferrableOutputStream[1];
> > > +               final Runnable tester = () -> {
> > > +                       try (final DeferrableOutputStream dos = new
> DeferrableOutputStream(-1, testFileSupplier)) {
> > > +                               streams[0] = dos;
> > > +                               assertFalse(dos.isInMemory());
> > > +                               assertNotNull(dos.getPath());
> > > +                               assertNull(dos.getBytes());
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +
> > > +                       final DeferrableOutputStream dos = streams[0];
> > > +                       assertFalse(dos.isInMemory());
> > > +                       assertNotNull(dos.getPath());
> > > +                       assertTrue(Files.isRegularFile(dos.getPath()));
> > > +                       final byte[] actual;
> > > +                       try (InputStream is = dos.getInputStream()) {
> > > +                               actual = read(is);
> > > +                       } catch (IOException ioe) {
> > > +                               throw new UncheckedIOException(ioe);
> > > +                       }
> > > +                       final byte[] expect =
> "".getBytes(StandardCharsets.UTF_8);
> > > +                       assertArrayEquals(expect, actual);
> > > +               };
> > > +               tester.run();
> > > +       }
> > > +
> > > +       protected byte[] read(InputStream pIs) throws IOException {
> > > +               final ByteArrayOutputStream baos = new
> ByteArrayOutputStream();
> > > +               final byte[] buffer = new byte[8192];
> > > +               for (;;) {
> > > +                       final int res = pIs.read(buffer);
> > > +                       if (res == -1) {
> > > +                               return baos.toByteArray();
> > > +                       } else if (res > 0) {
> > > +                               baos.write(buffer, 0, res);
> > > +                       }
> > > +               }
> > > +       }
> > > +}
> > >
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
> > For additional commands, e-mail: dev-h...@commons.apache.org
> >
>
>
> --
> The woman was born in a full-blown thunderstorm. She probably told it
> to be quiet. It probably did. (Robert Jordan, Winter's heart)
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
> For additional commands, e-mail: dev-h...@commons.apache.org
>
>

Reply via email to