View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
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.CharacterCodingException;
22  import java.nio.charset.Charset;
23  import java.nio.charset.CharsetEncoder;
24  import java.nio.charset.CoderResult;
25  
26  /**
27   * Helper class to encode text to binary data without allocating temporary objects.
28   *
29   * @since 2.6
30   */
31  public class TextEncoderHelper {
32  
33      private TextEncoderHelper() {
34      }
35  
36      static void encodeTextFallBack(final Charset charset, final StringBuilder text,
37              final ByteBufferDestination destination) {
38          final byte[] bytes = text.toString().getBytes(charset);
39          synchronized (destination) {
40              ByteBuffer buffer = destination.getByteBuffer();
41              int offset = 0;
42              do {
43                  final int length = Math.min(bytes.length - offset, buffer.remaining());
44                  buffer.put(bytes, offset, length);
45                  offset += length;
46                  if (offset < bytes.length) {
47                      buffer = destination.drain(buffer);
48                  }
49              } while (offset < bytes.length);
50          }
51      }
52  
53      static void encodeTextWithCopy(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer temp,
54              final StringBuilder text, final ByteBufferDestination destination) {
55  
56          encodeText(charsetEncoder, charBuf, temp, text, destination);
57          copyDataToDestination(temp, destination);
58      }
59  
60      private static void copyDataToDestination(final ByteBuffer temp, final ByteBufferDestination destination) {
61          synchronized (destination) {
62              ByteBuffer destinationBuffer = destination.getByteBuffer();
63              if (destinationBuffer != temp) { // still need to write to the destination
64                  temp.flip();
65                  if (temp.remaining() > destinationBuffer.remaining()) {
66                      destinationBuffer = destination.drain(destinationBuffer);
67                  }
68                  destinationBuffer.put(temp);
69                  temp.clear();
70              }
71          }
72      }
73  
74      static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer byteBuf,
75              final StringBuilder text, final ByteBufferDestination destination) {
76          charsetEncoder.reset();
77          ByteBuffer temp = byteBuf; // may be the destination's buffer or a temporary buffer
78          int start = 0;
79          int todoChars = text.length();
80          boolean endOfInput = true;
81          do {
82              charBuf.clear(); // reset character buffer position to zero, limit to capacity
83              final int copied = copy(text, start, charBuf);
84              start += copied;
85              todoChars -= copied;
86              endOfInput = todoChars <= 0;
87  
88              charBuf.flip(); // prepare for reading: set limit to position, position to zero
89              temp = encode(charsetEncoder, charBuf, endOfInput, destination, temp);
90          } while (!endOfInput);
91      }
92  
93      /**
94       * For testing purposes only.
95       */
96      @Deprecated
97      public static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf,
98              final ByteBufferDestination destination) {
99          synchronized (destination) {
100             charsetEncoder.reset();
101             final ByteBuffer byteBuf = destination.getByteBuffer();
102             encode(charsetEncoder, charBuf, true, destination, byteBuf);
103         }
104     }
105 
106     private static ByteBuffer encode(final CharsetEncoder charsetEncoder, final CharBuffer charBuf,
107             final boolean endOfInput, final ByteBufferDestination destination, ByteBuffer byteBuf) {
108         try {
109             byteBuf = encodeAsMuchAsPossible(charsetEncoder, charBuf, endOfInput, destination, byteBuf);
110             if (endOfInput) {
111                 byteBuf = flushRemainingBytes(charsetEncoder, destination, byteBuf);
112             }
113         } catch (final CharacterCodingException ex) {
114             throw new IllegalStateException(ex);
115         }
116         return byteBuf;
117     }
118 
119     private static ByteBuffer encodeAsMuchAsPossible(final CharsetEncoder charsetEncoder, final CharBuffer charBuf,
120             final boolean endOfInput, final ByteBufferDestination destination, ByteBuffer temp)
121             throws CharacterCodingException {
122         CoderResult result;
123         do {
124             result = charsetEncoder.encode(charBuf, temp, endOfInput);
125             temp = drainIfByteBufferFull(destination, temp, result);
126         } while (result.isOverflow()); // byte buffer has been drained: retry
127         if (!result.isUnderflow()) { // we should have fully read the char buffer contents
128             result.throwException();
129         }
130         return temp;
131     }
132 
133     private static ByteBuffer drainIfByteBufferFull(final ByteBufferDestination destination, ByteBuffer temp, final CoderResult result) {
134         if (result.isOverflow()) { // byte buffer full
135 
136             // SHOULD NOT HAPPEN:
137             // CALLER SHOULD ONLY PASS TEMP ByteBuffer LARGE ENOUGH TO ENCODE ALL CHARACTERS,
138             // AND LOCK ON THE DESTINATION IF THIS IS NOT POSSIBLE
139             ByteBuffer destinationBuffer = destination.getByteBuffer();
140             if (destinationBuffer != temp) {
141                 temp.flip();
142                 destinationBuffer.put(temp);
143                 temp.clear();
144             }
145             // destination consumes contents
146             // and returns byte buffer with more capacity
147             destinationBuffer = destination.drain(destinationBuffer);
148             temp = destinationBuffer;
149         }
150         return temp;
151     }
152 
153     private static ByteBuffer flushRemainingBytes(final CharsetEncoder charsetEncoder,
154             final ByteBufferDestination destination, ByteBuffer temp)
155             throws CharacterCodingException {
156         CoderResult result;
157         do {
158             // write any final bytes to the output buffer once the overall input sequence has been read
159             result = charsetEncoder.flush(temp);
160             temp = drainIfByteBufferFull(destination, temp, result);
161         } while (result.isOverflow()); // byte buffer has been drained: retry
162         if (!result.isUnderflow()) { // we should have fully flushed the remaining bytes
163             result.throwException();
164         }
165         return temp;
166     }
167 
168     /**
169      * Copies characters from the StringBuilder into the CharBuffer,
170      * starting at the specified offset and ending when either all
171      * characters have been copied or when the CharBuffer is full.
172      *
173      * @return the number of characters that were copied
174      */
175     static int copy(final StringBuilder source, final int offset, final CharBuffer destination) {
176         final int length = Math.min(source.length() - offset, destination.remaining());
177         final char[] array = destination.array();
178         final int start = destination.position();
179         source.getChars(offset, offset + length, array, start);
180         destination.position(start + length);
181         return length;
182     }
183 }