Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ed20f2a
[IO-856] Try test on all OSs for GitHub CI
garydgregory Nov 10, 2024
cb5359f
Merge remote-tracking branch 'upstream/master'
garydgregory Oct 19, 2025
3fe03be
Merge remote-tracking branch 'upstream/master'
garydgregory Nov 3, 2025
b5747b1
Merge remote-tracking branch 'upstream/master'
garydgregory Dec 7, 2025
515ae1e
Merge remote-tracking branch 'upstream/master'
garydgregory Dec 10, 2025
8192a78
Merge remote-tracking branch 'upstream/master'
garydgregory Dec 13, 2025
c780069
Merge remote-tracking branch 'upstream/master'
garydgregory Dec 14, 2025
6924d71
Merge remote-tracking branch 'upstream/master'
garydgregory Jan 1, 2026
3b77da2
Merge remote-tracking branch 'upstream/master'
garydgregory Feb 5, 2026
abdd9e1
Merge remote-tracking branch 'upstream/master'
garydgregory Feb 15, 2026
fb91287
Merge remote-tracking branch 'upstream/master'
garydgregory Feb 19, 2026
b165180
Merge remote-tracking branch 'upstream/master'
garydgregory Feb 21, 2026
2dd0ddf
Merge remote-tracking branch 'upstream/master'
garydgregory Feb 21, 2026
bd3f8b5
Merge remote-tracking branch 'upstream/master'
garydgregory Feb 22, 2026
b34ea84
Merge remote-tracking branch 'upstream/master'
garydgregory Mar 25, 2026
c74f241
Merge remote-tracking branch 'upstream/master'
garydgregory Mar 25, 2026
1d2b754
Merge remote-tracking branch 'upstream/master'
garydgregory Mar 26, 2026
c884a14
Merge remote-tracking branch 'upstream/master'
garydgregory Mar 26, 2026
02cab72
Merge remote-tracking branch 'upstream/master'
garydgregory Mar 26, 2026
cd3271f
Merge remote-tracking branch 'upstream/master'
garydgregory Mar 28, 2026
c6a8cba
Merge remote-tracking branch 'upstream/master'
garydgregory Mar 30, 2026
21b928b
Merge remote-tracking branch 'upstream/master'
garydgregory Apr 5, 2026
b3f9e1b
Add FlushShieldOutputStream
garydgregory Apr 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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
*
* https://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.io.output;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.commons.io.build.AbstractStreamBuilder;

/**
* Re-implements {@link FilterOutputStream#flush()} to do nothing.
*
* @since 2.22.0
*/
public final class FlushShieldOutputStream extends ProxyOutputStream {

// @formatter:off
/**
* Builds a new {@link FlushShieldOutputStream}.
*
* <p>
* Using File IO:
* </p>
* <pre>{@code
* FlushShieldOutputStream s = FlushShieldOutputStream.builder()
* .setPath("over/there.out")
* .setBufferSize(8192)
* .get();
* }
* </pre>
* <p>
* Using NIO Path:
* </p>
* <pre>{@code
* FlushShieldOutputStream s = FlushShieldOutputStream.builder()
* .setPath("over/there.out")
* .setBufferSize(8192)
* .get();
* }
* </pre>
*
* @see #get()
* @since 2.13.0
*/
// @formatter:on
public static class Builder extends AbstractStreamBuilder<FlushShieldOutputStream, Builder> {

/**
* Constructs a new builder of {@link FlushShieldOutputStream}.
*/
public Builder() {
// empty
}

/**
* Builds a new {@link FlushShieldOutputStream}.
*
* @return a new instance.
* @throws IllegalStateException if the {@code origin} is {@code null}.
* @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
* @throws IOException if an I/O error occurs converting to an {@link OutputStream} using {@link #getOutputStream()}.
* @see #getOutputStream()
* @see #getBufferSize()
* @see #getUnchecked()
*/
@Override
public FlushShieldOutputStream get() throws IOException {
return new FlushShieldOutputStream(this);
}

}

/**
* Constructs a new builder of {@link FlushShieldOutputStream}.
*
* @return a new builder of {@link FlushShieldOutputStream}.
*/
public static Builder builder() {
return new Builder();
}

@SuppressWarnings("resource") // caller closes.
private FlushShieldOutputStream(final Builder builder) throws IOException {
super(builder.getOutputStream());
}

/**
* Constructs a {@code FlushShieldOutputStream} filter for the specified underlying output stream.
*
* @param out the underlying output stream to be assigned to the field {@code this.out} for later use, or {@code null} if this instance is to be created
* without an underlying stream.
*/
public FlushShieldOutputStream(final OutputStream out) {
super(out);
}

@Override
public void flush() throws IOException {
// shield: do nothing.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* 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
*
* https://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.io.output;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
* Tests {@link FlushShieldOutputStream}.
*/
class FlushShieldOutputStreamTest {

/** Tracks whether the underlying stream's close() was called. */
private AtomicBoolean closed;

/** Tracks whether the underlying stream's flush() was called. */
private AtomicBoolean flushed;

/** The stream under test. */
private FlushShieldOutputStream shielded;

/** The underlying byte-array-backed stream used as the delegate. */
private ByteArrayOutputStream target;

@BeforeEach
void setUp() {
flushed = new AtomicBoolean();
closed = new AtomicBoolean();
target = new ByteArrayOutputStream() {

@Override
public void close() throws IOException {
closed.set(true);
super.close();
}

@Override
public void flush() throws IOException {
flushed.set(true);
super.flush();
}
};
shielded = new FlushShieldOutputStream(target);
}

@Test
void testBuilderGet() throws IOException {
assertNotNull(FlushShieldOutputStream.builder().setOutputStream(target).get());
}

@Test
void testBuilderWithOutputStream() throws IOException {
try (FlushShieldOutputStream built = FlushShieldOutputStream.builder().setOutputStream(target).get()) {
assertNotNull(built);
assertInstanceOf(FlushShieldOutputStream.class, built);
// flush must be shielded for builder-created instances too
built.flush();
assertFalse(flushed.get(), "flush() via builder instance must NOT reach the underlying stream.");
// writes must still work
built.write('B');
assertEquals(1, target.size());
assertEquals('B', target.toByteArray()[0]);
}
}

@Test
void testCloseReachesUnderlying() throws IOException {
assertFalse(closed.get(), "close should not have been called yet.");
shielded.close();
assertTrue(closed.get(), "close() must reach the underlying stream.");
}

@Test
void testFlushCanBeCalledMultipleTimes() throws IOException {
shielded.flush();
shielded.flush();
shielded.flush();
assertFalse(flushed.get(), "repeated flush() calls must NOT reach the underlying stream.");
}

@Test
void testFlushDoesNotDelegateToUnderlying() throws IOException {
assertFalse(flushed.get(), "flush should not have been called yet.");
shielded.flush();
assertFalse(flushed.get(), "flush() must NOT reach the underlying stream.");
}

@Test
void testWriteByteArray() throws IOException {
final byte[] data = { 'H', 'i' };
shielded.write(data);
assertEquals(2, target.size());
assertArrayEquals(data, target.toByteArray());
}

@Test
void testWriteByteArrayWithOffset() throws IOException {
final byte[] data = { 'X', 'Y', 'Z' };
shielded.write(data, 1, 2);
assertEquals(2, target.size());
assertArrayEquals(new byte[] { 'Y', 'Z' }, target.toByteArray());
}

@Test
void testWriteInt() throws IOException {
shielded.write('A');
assertEquals(1, target.size());
assertEquals('A', target.toByteArray()[0]);
}
}
Loading