This is an automated email from the ASF dual-hosted git repository. jungm pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/master by this push: new 89492c9b [JOHNZON-404] don't rely on jdk internals for BoundedOutputStreamWriter (#128) 89492c9b is described below commit 89492c9b144995ee3bd3cf243dfe2010bab209cf Author: Markus Jung <ju...@apache.org> AuthorDate: Thu Jun 27 14:50:36 2024 +0200 [JOHNZON-404] don't rely on jdk internals for BoundedOutputStreamWriter (#128) --- .github/workflows/maven.yml | 3 +- .../johnzon/core/JsonGeneratorFactoryImpl.java | 8 +-- .../main/java/org/apache/johnzon/core/Snippet.java | 14 ++-- .../johnzon/core/io/BoundedOutputStreamWriter.java | 80 ++++++++-------------- .../core/io/BoundedOutputStreamWriterTest.java | 7 +- 5 files changed, 47 insertions(+), 65 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 19cb2ddd..3dc3fe18 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -5,12 +5,11 @@ on: [push, pull_request] jobs: build: name: Test with Java ${{ matrix.jdk }} - #runs-on: ${{ matrix.os }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - jdk: [ '11', '17', '21' ] + jdk: [ '11', '17', '21', '22' ] dist: [ 'zulu' ] steps: diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorFactoryImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorFactoryImpl.java index e5939aa4..641efbf2 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorFactoryImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorFactoryImpl.java @@ -76,10 +76,10 @@ public class JsonGeneratorFactoryImpl extends AbstractJsonFactory implements Jso @Override public JsonGenerator createGenerator(final OutputStream out) { return new JsonGeneratorImpl( - boundedOutputStreamWriter <= 0 ? - new OutputStreamWriter(out, defaultEncoding) : - new BoundedOutputStreamWriter(out, defaultEncoding, boundedOutputStreamWriter), - getBufferProvider(out), pretty); + boundedOutputStreamWriter <= 0 ? + new OutputStreamWriter(out, defaultEncoding) : + new BoundedOutputStreamWriter(out, defaultEncoding, boundedOutputStreamWriter), + getBufferProvider(out), pretty); } @Override diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/Snippet.java b/johnzon-core/src/main/java/org/apache/johnzon/core/Snippet.java index 02a3b54f..058bbfcf 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/Snippet.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/Snippet.java @@ -28,6 +28,7 @@ import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.io.Writer; +import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; @@ -300,12 +301,13 @@ public class Snippet { public SnippetWriter(final int max) { this.max = max; this.buffer = new ByteArrayOutputStream(max); - this.mode = new Writing(max, new BoundedOutputStreamWriter( - buffer, - JsonGeneratorFactoryImpl.class.isInstance(generatorFactory) ? - JsonGeneratorFactoryImpl.class.cast(generatorFactory).getDefaultEncoding() : - UTF_8, - max)); + + Charset encoding = UTF_8; + if (JsonGeneratorFactoryImpl.class.isInstance(generatorFactory)) { + encoding = JsonGeneratorFactoryImpl.class.cast(generatorFactory).getDefaultEncoding(); + } + + this.mode = new Writing(max, new BoundedOutputStreamWriter(buffer, encoding, max)); } public String get() { diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/io/BoundedOutputStreamWriter.java b/johnzon-core/src/main/java/org/apache/johnzon/core/io/BoundedOutputStreamWriter.java index a96ca757..897916d5 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/io/BoundedOutputStreamWriter.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/io/BoundedOutputStreamWriter.java @@ -18,81 +18,61 @@ */ package org.apache.johnzon.core.io; +import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.Writer; -import java.nio.channels.Channels; import java.nio.charset.Charset; -import java.nio.charset.CodingErrorAction; /** - * {@link java.io.OutputStreamWriter} delegating directly to a sun.nio.cs.StreamEncoder with a controlled underlying buffer size. + * A {@link BufferedWriter} that wraps an {@link OutputStreamWriter} and automatically flushes it when flushing its internal buffer. * It enables to wrap an {@link OutputStream} as a {@link Writer} but with a faster feedback than a default - * {@link java.io.OutputStreamWriter} which uses a 8k buffer by default (encapsulated). - * <p> - * Note: the "flush error" can be of 2 characters (lcb in StreamEncoder) but we can't do much better when encoding. + * {@link OutputStreamWriter} which uses a 8k buffer by default (encapsulated). */ -public class BoundedOutputStreamWriter extends Writer { - private final Writer delegate; +public class BoundedOutputStreamWriter extends BufferedWriter { + private final int bufferSize; - public BoundedOutputStreamWriter(final OutputStream outputStream, - final Charset charset, - final int maxSize) { - delegate = Channels.newWriter( - Channels.newChannel(outputStream), - charset.newEncoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE), - maxSize); - } + private int writtenSinceLastFlush = 0; - @Override - public void write(final int c) throws IOException { - delegate.write(c); - } + public BoundedOutputStreamWriter(OutputStream outputStream, Charset charset, int maxSize) { + super(new OutputStreamWriter(outputStream, charset), maxSize); - @Override - public void write(final char[] chars, final int off, final int len) throws IOException { - delegate.write(chars, off, len); + this.bufferSize = maxSize; } - @Override - public void write(final String str, final int off, final int len) throws IOException { - delegate.write(str, off, len); - } + // Only methods that are directly modifying the internal buffer in BufferedWriter should be overwritten here, + // otherwise we might track the same char being written twice @Override - public void flush() throws IOException { - delegate.flush(); - } + public void write(String s, int off, int len) throws IOException { + autoFlush(); + super.write(s, off, len); - @Override - public void close() throws IOException { - delegate.close(); + writtenSinceLastFlush += len; } @Override - public void write(char[] cbuf) throws IOException { - delegate.write(cbuf); - } + public void write(char[] cbuf, int off, int len) throws IOException { + autoFlush(); + super.write(cbuf, off, len); - @Override - public void write(final String str) throws IOException { - delegate.write(str); + writtenSinceLastFlush += len; } @Override - public Writer append(final CharSequence csq) throws IOException { - return delegate.append(csq); - } + public void write(int c) throws IOException { + autoFlush(); + super.write(c); - @Override - public Writer append(final CharSequence csq, final int start, final int end) throws IOException { - return delegate.append(csq, start, end); + writtenSinceLastFlush += 1; } - @Override - public Writer append(final char c) throws IOException { - return delegate.append(c); + private void autoFlush() throws IOException { + if (writtenSinceLastFlush >= bufferSize) { + flush(); + + writtenSinceLastFlush = 0; + } } } diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/io/BoundedOutputStreamWriterTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/io/BoundedOutputStreamWriterTest.java index 2b8f6f6c..a10987f8 100644 --- a/johnzon-core/src/test/java/org/apache/johnzon/core/io/BoundedOutputStreamWriterTest.java +++ b/johnzon-core/src/test/java/org/apache/johnzon/core/io/BoundedOutputStreamWriterTest.java @@ -22,6 +22,7 @@ import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.Writer; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; @@ -31,7 +32,7 @@ public class BoundedOutputStreamWriterTest { @Test public void write() throws IOException { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (final BoundedOutputStreamWriter writer = new BoundedOutputStreamWriter(outputStream, UTF_8, 10)) { + try (final Writer writer = new BoundedOutputStreamWriter(outputStream, UTF_8, 10)) { writer.write("ok"); writer.write('1'); } @@ -42,7 +43,7 @@ public class BoundedOutputStreamWriterTest { @Test public void sizeLimit() throws IOException { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (final BoundedOutputStreamWriter writer = new BoundedOutputStreamWriter(outputStream, UTF_8, 10)) { + try (final Writer writer = new BoundedOutputStreamWriter(outputStream, UTF_8, 10)) { writer.write("1234567890"); assertEquals(0, outputStream.size()); // was not yet written since it matches buffer size writer.write('1'); @@ -55,7 +56,7 @@ public class BoundedOutputStreamWriterTest { @Test public void sizeLimit2() throws IOException { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (final BoundedOutputStreamWriter writer = new BoundedOutputStreamWriter(outputStream, UTF_8, 2)) { + try (final Writer writer = new BoundedOutputStreamWriter(outputStream, UTF_8, 10)) { writer.write("1234567890"); writer.write('1'); }