From 2c40c2f0f4973c9f4a0338d86ae54242bf8c2a04 Mon Sep 17 00:00:00 2001 From: Andreas Hager Date: Tue, 23 May 2023 05:59:21 +0200 Subject: [PATCH 1/2] #228 simplify Utf8ByteOutput --- .../java/gg/jte/output/Utf8ByteOutput.java | 219 +----------------- .../gg/jte/output/Utf8ByteOutputTest.java | 4 +- 2 files changed, 14 insertions(+), 209 deletions(-) diff --git a/jte-runtime/src/main/java/gg/jte/output/Utf8ByteOutput.java b/jte-runtime/src/main/java/gg/jte/output/Utf8ByteOutput.java index be1e10b9..0da5c8d7 100644 --- a/jte-runtime/src/main/java/gg/jte/output/Utf8ByteOutput.java +++ b/jte-runtime/src/main/java/gg/jte/output/Utf8ByteOutput.java @@ -4,59 +4,28 @@ import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.List; /** - * Heavily optimized UTF-8 template output, designed to be CPU and memory friendly. + * UTF-8 template output, designed to be CPU and memory friendly. * You may want to use this class, if you write to a low-level binary output stream, and you need the exact content-size of the output. * * CAUTION: You must enable {@link gg.jte.TemplateEngine#setBinaryStaticContent(boolean)}, otherwise this class won't provide any benefits over {@link StringOutput}! */ public final class Utf8ByteOutput implements TemplateOutput { - private final int chunkSize; - - private final char[] tempBuffer; - private final int tempBufferSize; - - private ArrayList chunks; - private byte[] buffer; - private int lastIndex; - private int currentIndex; - - private char highSurrogate; - - /** - * Constructs an output with sane defaults - */ - public Utf8ByteOutput() { - this(1024, 512); - } - - /** - * Constructs an output with custom settings. - * This output maintains a list of binary chunks. Pre-encoded data is passed as is, while internal buffers are created for dynamic data as needed. - * @param chunkSize The size in bytes for chunks of dynamic data. - * @param tempBufferSize The size for the temporary buffer used for intermediate String encoding. - */ - public Utf8ByteOutput(int chunkSize, int tempBufferSize) { - this.chunkSize = chunkSize; - buffer = new byte[this.chunkSize]; - - this.tempBufferSize = tempBufferSize; - tempBuffer = new char[tempBufferSize]; - } + private final List chunks = new ArrayList<>(); /** * @return The amount of bytes written to this output. */ public int getContentLength() { - int contentLength = currentIndex - lastIndex; + int contentLength = 0; - if (chunks != null) { - for (Chunk chunk : chunks) { - contentLength += chunk.length; - } + for (byte[] chunk : chunks) { + contentLength += chunk.length; } return contentLength; @@ -78,188 +47,24 @@ public void writeTo(OutputStream os) throws IOException { * @throws IOException in case the consume operation fails */ public void writeTo(DataConsumer dataConsumer) throws IOException { - if (chunks != null) { - for (Chunk chunk : chunks) { - dataConsumer.accept(chunk.data, chunk.startIndex, chunk.length); - } - } - - int remaining = currentIndex - lastIndex; - if (remaining > 0) { - dataConsumer.accept(buffer, lastIndex, remaining); + for (byte[] chunk : chunks) { + dataConsumer.accept(chunk, 0, chunk.length); } } @Override public void writeContent(String s) { - int len = s.length(); - for (int i = 0; i < len; i += tempBufferSize) { - int size = Math.min(tempBufferSize, len - i); - s.getChars(i, i + size, tempBuffer, 0); - write(tempBuffer, 0, size); - } + chunks.add(s.getBytes(StandardCharsets.UTF_8)); } @Override public void writeContent(String s, int beginIndex, int endIndex) { - for (int i = beginIndex; i < endIndex; i += tempBufferSize) { - int size = Math.min(tempBufferSize, endIndex - i); - s.getChars(i, i + size, tempBuffer, 0); - write(tempBuffer, 0, size); - } + chunks.add(s.substring(beginIndex, endIndex).getBytes(StandardCharsets.UTF_8)); } @Override public void writeBinaryContent(byte[] value) { - if (value.length < 16) { - doAppend(value); // Don't waste chunks if array is very small. - } else { - if (lastIndex < currentIndex) { - addCurrentChunk(); - lastIndex = currentIndex; - } - - addChunk(new Chunk(value, 0, value.length)); - } - } - - @Override - public void writeUserContent(boolean value) { - appendLatin1(String.valueOf(value)); - } - - @Override - public void writeUserContent(byte value) { - appendLatin1(Byte.toString(value)); - } - - @Override - public void writeUserContent(char value) { - write(value); - } - - @Override - public void writeUserContent(int value) { - appendLatin1(Integer.toString(value)); - } - - @Override - public void writeUserContent(long value) { - appendLatin1(Long.toString(value)); - } - - @Override - public void writeUserContent(float value) { - appendLatin1(Float.toString(value)); - } - - @Override - public void writeUserContent(double value) { - appendLatin1(Double.toString(value)); - } - - private void write(@SuppressWarnings("NullableProblems") char[] buffer, int off, int len) { - int i = off; - len += off; - - while (i < len) { - write(buffer[i++]); - } - } - - private void write(char c) { - if (highSurrogate != 0) { - if (Character.isLowSurrogate(c)) { - appendUtf8CodePoint(Character.toCodePoint(highSurrogate, c)); - } else { - doAppend((byte)('�')); - appendUtf8Char(c); - } - highSurrogate = 0; - } else if (Character.isHighSurrogate(c)) { - highSurrogate = c; - } else { - appendUtf8Char(c); - } - } - - private void appendLatin1(String s) { - int len = s.length(); - - for (int i = 0; i < len; ++i) { - doAppend((byte) s.charAt(i)); - } - } - - private void appendUtf8Char(char c) { - if (c < 0x80) { - doAppend((byte) c); - } else if (c < 0x800) { - doAppend((byte) (0xc0 | c >> 6)); - doAppend((byte) (0x80 | c & 0x3f)); - } else { - doAppend((byte)(0xe0 | (c >> 12))); - doAppend((byte)(0x80 | ((c >> 6) & 0x3f))); - doAppend((byte)(0x80 | (c & 0x3f))); - } - } - - private void appendUtf8CodePoint(int c) { - doAppend((byte)(0xf0 | (c >> 18))); - doAppend((byte)(0x80 | ((c >> 12) & 0x3f))); - doAppend((byte)(0x80 | ((c >> 6) & 0x3f))); - doAppend((byte)(0x80 | (c & 0x3f))); - } - - private void doAppend(byte[] bytes) { - int length = bytes.length; - - if (currentIndex + length < chunkSize) { - System.arraycopy(bytes, 0, buffer, currentIndex, length); - currentIndex += length; - } else { - for (byte b : bytes) { - doAppend(b); - } - } - } - - private void doAppend(byte b) { - if (currentIndex == chunkSize) { - createNewChunk(); - } - buffer[currentIndex++] = b; - } - - private void createNewChunk() { - addCurrentChunk(); - - buffer = new byte[chunkSize]; - lastIndex = 0; - currentIndex = 0; - } - - private void addCurrentChunk() { - addChunk(new Chunk(buffer, lastIndex, currentIndex - lastIndex)); - } - - private void addChunk(Chunk chunk) { - if (chunks == null) { - chunks = new ArrayList<>(); - } - chunks.add(chunk); - } - - private static final class Chunk { - final byte[] data; - final int startIndex; - final int length; - - public Chunk(byte[] data, int startIndex, int length) { - this.data = data; - this.startIndex = startIndex; - this.length = length; - } + chunks.add(value); } @FunctionalInterface diff --git a/jte/src/test/java/gg/jte/output/Utf8ByteOutputTest.java b/jte/src/test/java/gg/jte/output/Utf8ByteOutputTest.java index 26beb89e..0d02522e 100644 --- a/jte/src/test/java/gg/jte/output/Utf8ByteOutputTest.java +++ b/jte/src/test/java/gg/jte/output/Utf8ByteOutputTest.java @@ -13,7 +13,7 @@ public class Utf8ByteOutputTest extends AbstractTemplateOutputTest Date: Tue, 23 May 2023 07:50:18 +0200 Subject: [PATCH 2/2] #225 StringOutput does not extend Writer --- .../main/java/gg/jte/output/StringOutput.java | 41 +------------------ 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/jte-runtime/src/main/java/gg/jte/output/StringOutput.java b/jte-runtime/src/main/java/gg/jte/output/StringOutput.java index 7fb9f06e..ef84c854 100644 --- a/jte-runtime/src/main/java/gg/jte/output/StringOutput.java +++ b/jte-runtime/src/main/java/gg/jte/output/StringOutput.java @@ -2,9 +2,7 @@ import gg.jte.TemplateOutput; -import java.io.Writer; - -public class StringOutput extends Writer implements TemplateOutput { +public class StringOutput implements TemplateOutput { private final StringBuilder stringBuilder; public StringOutput() { @@ -68,41 +66,4 @@ public String toString() { public void reset() { stringBuilder.setLength(0); } - - // Writer interface - - @Override - public void write(char[] buffer, int off, int len) { - stringBuilder.append(buffer, off, len); - } - - @Override - public void write(int c) { - stringBuilder.append((char)c); - } - - @Override - public void write(char[] cbuf) { - stringBuilder.append(cbuf); - } - - @Override - public void write(String str) { - stringBuilder.append(str); - } - - @Override - public void write(String str, int off, int len) { - stringBuilder.append(str, off, off + len); - } - - @Override - public void flush() { - // nothing to do - } - - @Override - public void close() { - // nothing to do - } }