1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.layout;
18
19 import org.apache.logging.log4j.core.util.Constants;
20 import org.apache.logging.log4j.status.StatusLogger;
21
22 import java.nio.ByteBuffer;
23 import java.nio.CharBuffer;
24 import java.nio.charset.Charset;
25 import java.nio.charset.CharsetEncoder;
26 import java.nio.charset.CodingErrorAction;
27 import java.util.Objects;
28
29
30
31
32 public class StringBuilderEncoder implements Encoder<StringBuilder> {
33
34 private static final int DEFAULT_BYTE_BUFFER_SIZE = 8 * 1024;
35 private final ThreadLocal<CharBuffer> charBufferThreadLocal = new ThreadLocal<>();
36 private final ThreadLocal<ByteBuffer> byteBufferThreadLocal = new ThreadLocal<>();
37 private final ThreadLocal<CharsetEncoder> charsetEncoderThreadLocal = new ThreadLocal<>();
38 private final Charset charset;
39 private final int charBufferSize;
40 private final int byteBufferSize;
41
42 public StringBuilderEncoder(final Charset charset) {
43 this(charset, Constants.ENCODER_CHAR_BUFFER_SIZE, DEFAULT_BYTE_BUFFER_SIZE);
44 }
45
46 public StringBuilderEncoder(final Charset charset, final int charBufferSize, final int byteBufferSize) {
47 this.charBufferSize = charBufferSize;
48 this.byteBufferSize = byteBufferSize;
49 this.charset = Objects.requireNonNull(charset, "charset");
50 }
51
52 @Override
53 public void encode(final StringBuilder source, final ByteBufferDestination destination) {
54 final ByteBuffer temp = getByteBuffer();
55 temp.clear();
56 temp.limit(Math.min(temp.capacity(), destination.getByteBuffer().capacity()));
57 final CharsetEncoder charsetEncoder = getCharsetEncoder();
58
59 final int estimatedBytes = estimateBytes(source.length(), charsetEncoder.maxBytesPerChar());
60 if (temp.remaining() < estimatedBytes) {
61 encodeSynchronized(getCharsetEncoder(), getCharBuffer(), source, destination);
62 } else {
63 encodeWithThreadLocals(charsetEncoder, getCharBuffer(), temp, source, destination);
64 }
65 }
66
67 private void encodeWithThreadLocals(final CharsetEncoder charsetEncoder, final CharBuffer charBuffer,
68 final ByteBuffer temp, final StringBuilder source, final ByteBufferDestination destination) {
69 try {
70 TextEncoderHelper.encodeTextWithCopy(charsetEncoder, charBuffer, temp, source, destination);
71 } catch (final Exception ex) {
72 ex.printStackTrace();
73 logEncodeTextException(ex, source, destination);
74 TextEncoderHelper.encodeTextFallBack(charset, source, destination);
75 }
76 }
77
78 private static int estimateBytes(final int charCount, final float maxBytesPerChar) {
79 return (int) (charCount * (double) maxBytesPerChar);
80 }
81
82 private void encodeSynchronized(final CharsetEncoder charsetEncoder, final CharBuffer charBuffer,
83 final StringBuilder source, final ByteBufferDestination destination) {
84 synchronized (destination) {
85 try {
86 TextEncoderHelper.encodeText(charsetEncoder, charBuffer, destination.getByteBuffer(), source,
87 destination);
88 } catch (final Exception ex) {
89 logEncodeTextException(ex, source, destination);
90 TextEncoderHelper.encodeTextFallBack(charset, source, destination);
91 }
92 }
93 }
94
95 private CharsetEncoder getCharsetEncoder() {
96 CharsetEncoder result = charsetEncoderThreadLocal.get();
97 if (result == null) {
98 result = charset.newEncoder().onMalformedInput(CodingErrorAction.REPLACE)
99 .onUnmappableCharacter(CodingErrorAction.REPLACE);
100 charsetEncoderThreadLocal.set(result);
101 }
102 return result;
103 }
104
105
106 private CharBuffer getCharBuffer() {
107 CharBuffer result = charBufferThreadLocal.get();
108 if (result == null) {
109 result = CharBuffer.wrap(new char[charBufferSize]);
110 charBufferThreadLocal.set(result);
111 }
112 return result;
113 }
114
115 private ByteBuffer getByteBuffer() {
116 ByteBuffer result = byteBufferThreadLocal.get();
117 if (result == null) {
118 result = ByteBuffer.wrap(new byte[byteBufferSize]);
119 byteBufferThreadLocal.set(result);
120 }
121 return result;
122 }
123
124 private void logEncodeTextException(final Exception ex, final StringBuilder text,
125 final ByteBufferDestination destination) {
126 StatusLogger.getLogger().error("Recovering from StringBuilderEncoder.encode('{}') error: {}", text, ex, ex);
127 }
128 }