/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache license, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the license for the specific language governing permissions and
 * limitations under the license.
 */
package org.apache.logging.log4j.core.layout;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;

Helper class to encode text to binary data without allocating temporary objects.
Since:2.6
/** * Helper class to encode text to binary data without allocating temporary objects. * * @since 2.6 */
public class TextEncoderHelper { private TextEncoderHelper() { } static void encodeTextFallBack(final Charset charset, final StringBuilder text, final ByteBufferDestination destination) { final byte[] bytes = text.toString().getBytes(charset); destination.writeBytes(bytes, 0, bytes.length); }
Converts the specified text to bytes and writes the resulting bytes to the specified destination. Attempts to postpone synchronizing on the destination as long as possible to minimize lock contention.
Params:
  • charsetEncoder – thread-local encoder instance for converting chars to bytes
  • charBuf – thread-local text buffer for converting text to bytes
  • byteBuf – thread-local buffer to temporarily hold converted bytes before copying them to the destination
  • text – the text to convert and write to the destination
  • destination – the destination to write the bytes to
Throws:
/** * Converts the specified text to bytes and writes the resulting bytes to the specified destination. * Attempts to postpone synchronizing on the destination as long as possible to minimize lock contention. * * @param charsetEncoder thread-local encoder instance for converting chars to bytes * @param charBuf thread-local text buffer for converting text to bytes * @param byteBuf thread-local buffer to temporarily hold converted bytes before copying them to the destination * @param text the text to convert and write to the destination * @param destination the destination to write the bytes to * @throws CharacterCodingException if conversion failed */
static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer byteBuf, final StringBuilder text, final ByteBufferDestination destination) throws CharacterCodingException { charsetEncoder.reset(); if (text.length() > charBuf.capacity()) { encodeChunkedText(charsetEncoder, charBuf, byteBuf, text, destination); return; } charBuf.clear(); text.getChars(0, text.length(), charBuf.array(), charBuf.arrayOffset()); charBuf.limit(text.length()); final CoderResult result = charsetEncoder.encode(charBuf, byteBuf, true); writeEncodedText(charsetEncoder, charBuf, byteBuf, destination, result); }
This method is called when the CharEncoder has encoded (but not yet flushed) content from the CharBuffer into the ByteBuffer. A CoderResult of UNDERFLOW means that the contents fit into the ByteBuffer and we can move on to the next step, flushing. Otherwise, we need to synchronize on the destination, copy the ByteBuffer to the destination and encode the remainder of the CharBuffer while holding the lock on the destination.
Since:2.9
/** * This method is called when the CharEncoder has encoded (but not yet flushed) content from the CharBuffer * into the ByteBuffer. A CoderResult of UNDERFLOW means that the contents fit into the ByteBuffer and we can move * on to the next step, flushing. Otherwise, we need to synchronize on the destination, copy the ByteBuffer to the * destination and encode the remainder of the CharBuffer while holding the lock on the destination. * * @since 2.9 */
private static void writeEncodedText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer byteBuf, final ByteBufferDestination destination, CoderResult result) { if (!result.isUnderflow()) { writeChunkedEncodedText(charsetEncoder, charBuf, destination, byteBuf, result); return; } result = charsetEncoder.flush(byteBuf); if (!result.isUnderflow()) { synchronized (destination) { flushRemainingBytes(charsetEncoder, destination, byteBuf); } return; } // Thread-safety note: no explicit synchronization on ByteBufferDestination below. This is safe, because // if the byteBuf is actually the destination's buffer, this method call should be protected with // synchronization on the destination object at some level, so the call to destination.getByteBuffer() should // be safe. If the byteBuf is an unrelated buffer, the comparison between the buffers should fail despite // destination.getByteBuffer() is not protected with the synchronization on the destination object. if (byteBuf != destination.getByteBuffer()) { byteBuf.flip(); destination.writeBytes(byteBuf); byteBuf.clear(); } }
This method is called when the CharEncoder has encoded (but not yet flushed) content from the CharBuffer into the ByteBuffer and we found that the ByteBuffer is too small to hold all the content. Therefore, we need to synchronize on the destination, copy the ByteBuffer to the destination and encode the remainder of the CharBuffer while holding the lock on the destination.
Since:2.9
/** * This method is called when the CharEncoder has encoded (but not yet flushed) content from the CharBuffer * into the ByteBuffer and we found that the ByteBuffer is too small to hold all the content. * Therefore, we need to synchronize on the destination, copy the ByteBuffer to the * destination and encode the remainder of the CharBuffer while holding the lock on the destination. * * @since 2.9 */
private static void writeChunkedEncodedText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBufferDestination destination, ByteBuffer byteBuf, final CoderResult result) { synchronized (destination) { byteBuf = writeAndEncodeAsMuchAsPossible(charsetEncoder, charBuf, true, destination, byteBuf, result); flushRemainingBytes(charsetEncoder, destination, byteBuf); } }
This method is called before the CharEncoder has encoded any content from the CharBuffer into the ByteBuffer, but we have already detected that the CharBuffer contents is too large to fit into the ByteBuffer. Therefore, at some point we need to synchronize on the destination, copy the ByteBuffer to the destination and encode the remainder of the CharBuffer while holding the lock on the destination.
Since:2.9
/** * This method is called <em>before</em> the CharEncoder has encoded any content from the CharBuffer * into the ByteBuffer, but we have already detected that the CharBuffer contents is too large to fit into the * ByteBuffer. Therefore, at some point we need to synchronize on the destination, copy the ByteBuffer to the * destination and encode the remainder of the CharBuffer while holding the lock on the destination. * * @since 2.9 */
private static void encodeChunkedText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, ByteBuffer byteBuf, final StringBuilder text, final ByteBufferDestination destination) { // LOG4J2-1874 ByteBuffer, CharBuffer and CharsetEncoder are thread-local, so no need to synchronize while // modifying these objects. Postpone synchronization until accessing the ByteBufferDestination. int start = 0; CoderResult result = CoderResult.UNDERFLOW; boolean endOfInput = false; while (!endOfInput && result.isUnderflow()) { charBuf.clear(); final int copied = copy(text, start, charBuf); start += copied; endOfInput = start >= text.length(); charBuf.flip(); result = charsetEncoder.encode(charBuf, byteBuf, endOfInput); } if (endOfInput) { writeEncodedText(charsetEncoder, charBuf, byteBuf, destination, result); return; } synchronized (destination) { byteBuf = writeAndEncodeAsMuchAsPossible(charsetEncoder, charBuf, endOfInput, destination, byteBuf, result); while (!endOfInput) { result = CoderResult.UNDERFLOW; while (!endOfInput && result.isUnderflow()) { charBuf.clear(); final int copied = copy(text, start, charBuf); start += copied; endOfInput = start >= text.length(); charBuf.flip(); result = charsetEncoder.encode(charBuf, byteBuf, endOfInput); } byteBuf = writeAndEncodeAsMuchAsPossible(charsetEncoder, charBuf, endOfInput, destination, byteBuf, result); } flushRemainingBytes(charsetEncoder, destination, byteBuf); } }
For testing purposes only.
/** * For testing purposes only. */
@Deprecated public static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBufferDestination destination) { charsetEncoder.reset(); synchronized (destination) { ByteBuffer byteBuf = destination.getByteBuffer(); byteBuf = encodeAsMuchAsPossible(charsetEncoder, charBuf, true, destination, byteBuf); flushRemainingBytes(charsetEncoder, destination, byteBuf); } }
Continues to write the contents of the ByteBuffer to the destination and encode more of the CharBuffer text into the ByteBuffer until the remaining encoded text fit into the ByteBuffer, at which point the ByteBuffer is returned (without flushing the CharEncoder).

This method is called when the CharEncoder has encoded (but not yet flushed) content from the CharBuffer into the ByteBuffer and we found that the ByteBuffer is too small to hold all the content.

Thread-safety note: This method should be called while synchronizing on the ByteBufferDestination.

Returns:the ByteBuffer resulting from draining the temporary ByteBuffer to the destination. In the case of a MemoryMappedFile, a remap() may have taken place and the returned ByteBuffer is now the MappedBuffer of the newly mapped region of the memory mapped file.
Since:2.9
/** * Continues to write the contents of the ByteBuffer to the destination and encode more of the CharBuffer text * into the ByteBuffer until the remaining encoded text fit into the ByteBuffer, at which point the ByteBuffer * is returned (without flushing the CharEncoder). * <p> * This method is called when the CharEncoder has encoded (but not yet flushed) content from the CharBuffer * into the ByteBuffer and we found that the ByteBuffer is too small to hold all the content. * </p><p> * Thread-safety note: This method should be called while synchronizing on the ByteBufferDestination. * </p> * @return the ByteBuffer resulting from draining the temporary ByteBuffer to the destination. In the case * of a MemoryMappedFile, a remap() may have taken place and the returned ByteBuffer is now the * MappedBuffer of the newly mapped region of the memory mapped file. * @since 2.9 */
private static ByteBuffer writeAndEncodeAsMuchAsPossible(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final boolean endOfInput, final ByteBufferDestination destination, ByteBuffer temp, CoderResult result) { while (true) { temp = drainIfByteBufferFull(destination, temp, result); if (!result.isOverflow()) { break; } result = charsetEncoder.encode(charBuf, temp, endOfInput); } if (!result.isUnderflow()) { // we should have fully read the char buffer contents throwException(result); } return temp; } // @since 2.9 private static void throwException(final CoderResult result) { try { result.throwException(); } catch (final CharacterCodingException e) { throw new IllegalStateException(e); } } private static ByteBuffer encodeAsMuchAsPossible(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final boolean endOfInput, final ByteBufferDestination destination, ByteBuffer temp) { CoderResult result; do { result = charsetEncoder.encode(charBuf, temp, endOfInput); temp = drainIfByteBufferFull(destination, temp, result); } while (result.isOverflow()); // byte buffer has been drained: retry if (!result.isUnderflow()) { // we should have fully read the char buffer contents throwException(result); } return temp; }
If the CoderResult indicates the ByteBuffer is full, synchronize on the destination and write the content of the ByteBuffer to the destination. If the specified ByteBuffer is owned by the destination, we have reached the end of a MappedBuffer and we call drain() on the destination to remap().

If the CoderResult indicates more can be encoded, this method does nothing and returns the temp ByteBuffer.

Params:
  • destination – the destination to write bytes to
  • temp – the ByteBuffer containing the encoded bytes. May be a temporary buffer or may be the ByteBuffer of the ByteBufferDestination
  • result – the CoderResult from the CharsetEncoder
Returns:the ByteBuffer to encode into for the remainder of the text
/** * If the CoderResult indicates the ByteBuffer is full, synchronize on the destination and write the content * of the ByteBuffer to the destination. If the specified ByteBuffer is owned by the destination, we have * reached the end of a MappedBuffer and we call drain() on the destination to remap(). * <p> * If the CoderResult indicates more can be encoded, this method does nothing and returns the temp ByteBuffer. * </p> * * @param destination the destination to write bytes to * @param temp the ByteBuffer containing the encoded bytes. May be a temporary buffer or may be the ByteBuffer of * the ByteBufferDestination * @param result the CoderResult from the CharsetEncoder * @return the ByteBuffer to encode into for the remainder of the text */
private static ByteBuffer drainIfByteBufferFull(final ByteBufferDestination destination, final ByteBuffer temp, final CoderResult result) { if (result.isOverflow()) { // byte buffer full // all callers already synchronize on destination but for safety ensure we are synchronized because // below calls to drain() may cause destination to swap in a new ByteBuffer object synchronized (destination) { final ByteBuffer destinationBuffer = destination.getByteBuffer(); if (destinationBuffer != temp) { temp.flip(); ByteBufferDestinationHelper.writeToUnsynchronized(temp, destination); temp.clear(); return destination.getByteBuffer(); } else { return destination.drain(destinationBuffer); } } } else { return temp; } } private static void flushRemainingBytes(final CharsetEncoder charsetEncoder, final ByteBufferDestination destination, ByteBuffer temp) { CoderResult result; do { // write any final bytes to the output buffer once the overall input sequence has been read result = charsetEncoder.flush(temp); temp = drainIfByteBufferFull(destination, temp, result); } while (result.isOverflow()); // byte buffer has been drained: retry if (!result.isUnderflow()) { // we should have fully flushed the remaining bytes throwException(result); } if (temp.remaining() > 0 && temp != destination.getByteBuffer()) { temp.flip(); ByteBufferDestinationHelper.writeToUnsynchronized(temp, destination); temp.clear(); } }
Copies characters from the StringBuilder into the CharBuffer, starting at the specified offset and ending when either all characters have been copied or when the CharBuffer is full.
Returns:the number of characters that were copied
/** * Copies characters from the StringBuilder into the CharBuffer, * starting at the specified offset and ending when either all * characters have been copied or when the CharBuffer is full. * * @return the number of characters that were copied */
static int copy(final StringBuilder source, final int offset, final CharBuffer destination) { final int length = Math.min(source.length() - offset, destination.remaining()); final char[] array = destination.array(); final int start = destination.position(); source.getChars(offset, offset + length, array, destination.arrayOffset() + start); destination.position(start + length); return length; } }