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