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 java.nio.ByteBuffer;
20 import java.nio.CharBuffer;
21 import java.nio.charset.Charset;
22 import java.nio.charset.CharsetEncoder;
23 import java.nio.charset.CodingErrorAction;
24 import java.util.Objects;
25
26 import org.apache.logging.log4j.core.util.Constants;
27 import org.apache.logging.log4j.status.StatusLogger;
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 logEncodeTextException(ex, source, destination);
73 TextEncoderHelper.encodeTextFallBack(charset, source, destination);
74 }
75 }
76
77 private static int estimateBytes(final int charCount, final float maxBytesPerChar) {
78 return (int) (charCount * (double) maxBytesPerChar);
79 }
80
81 private void encodeSynchronized(final CharsetEncoder charsetEncoder, final CharBuffer charBuffer,
82 final StringBuilder source, final ByteBufferDestination destination) {
83 synchronized (destination) {
84 try {
85 TextEncoderHelper.encodeText(charsetEncoder, charBuffer, destination.getByteBuffer(), source,
86 destination);
87 } catch (final Exception ex) {
88 logEncodeTextException(ex, source, destination);
89 TextEncoderHelper.encodeTextFallBack(charset, source, destination);
90 }
91 }
92 }
93
94 private CharsetEncoder getCharsetEncoder() {
95 CharsetEncoder result = charsetEncoderThreadLocal.get();
96 if (result == null) {
97 result = charset.newEncoder().onMalformedInput(CodingErrorAction.REPLACE)
98 .onUnmappableCharacter(CodingErrorAction.REPLACE);
99 charsetEncoderThreadLocal.set(result);
100 }
101 return result;
102 }
103
104
105 private CharBuffer getCharBuffer() {
106 CharBuffer result = charBufferThreadLocal.get();
107 if (result == null) {
108 result = CharBuffer.wrap(new char[charBufferSize]);
109 charBufferThreadLocal.set(result);
110 }
111 return result;
112 }
113
114 private ByteBuffer getByteBuffer() {
115 ByteBuffer result = byteBufferThreadLocal.get();
116 if (result == null) {
117 result = ByteBuffer.wrap(new byte[byteBufferSize]);
118 byteBufferThreadLocal.set(result);
119 }
120 return result;
121 }
122
123 private void logEncodeTextException(final Exception ex, final StringBuilder text,
124 final ByteBufferDestination destination) {
125 StatusLogger.getLogger().error("Recovering from StringBuilderEncoder.encode('{}') error: {}", text, ex, ex);
126 }
127 }