/* * 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 */ public class TextEncoderHelper { private TextEncoderHelper() { } static void encodeTextFallBack(final Charset charset, final StringBuilder text, final ByteBufferDestination destination) { final byte[] bytes = text.toString().getBytes(charset); synchronized (destination) { ByteBuffer buffer = destination.getByteBuffer(); int offset = 0; do { final int length = Math.min(bytes.length - offset, buffer.remaining()); buffer.put(bytes, offset, length); offset += length; if (offset < bytes.length) { buffer = destination.drain(buffer); } } while (offset < bytes.length); } } static void encodeTextWithCopy(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer temp, final StringBuilder text, final ByteBufferDestination destination) { encodeText(charsetEncoder, charBuf, temp, text, destination); copyDataToDestination(temp, destination); } private static void copyDataToDestination(final ByteBuffer temp, final ByteBufferDestination destination) { synchronized (destination) { ByteBuffer destinationBuffer = destination.getByteBuffer(); if (destinationBuffer != temp) { // still need to write to the destination temp.flip(); if (temp.remaining() > destinationBuffer.remaining()) { destinationBuffer = destination.drain(destinationBuffer); } destinationBuffer.put(temp); temp.clear(); } } } static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer byteBuf, final StringBuilder text, final ByteBufferDestination destination) { charsetEncoder.reset(); ByteBuffer temp = byteBuf; // may be the destination's buffer or a temporary buffer int start = 0; int todoChars = text.length(); boolean endOfInput = true; do { charBuf.clear(); // reset character buffer position to zero, limit to capacity final int copied = copy(text, start, charBuf); start += copied; todoChars -= copied; endOfInput = todoChars <= 0; charBuf.flip(); // prepare for reading: set limit to position, position to zero temp = encode(charsetEncoder, charBuf, endOfInput, destination, temp); } while (!endOfInput); } /** * For testing purposes only. */ @Deprecated public static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBufferDestination destination) { synchronized (destination) { charsetEncoder.reset(); final ByteBuffer byteBuf = destination.getByteBuffer(); encode(charsetEncoder, charBuf, true, destination, byteBuf); } } private static ByteBuffer encode(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final boolean endOfInput, final ByteBufferDestination destination, ByteBuffer byteBuf) { try { byteBuf = encodeAsMuchAsPossible(charsetEncoder, charBuf, endOfInput, destination, byteBuf); if (endOfInput) { byteBuf = flushRemainingBytes(charsetEncoder, destination, byteBuf); } } catch (final CharacterCodingException ex) { throw new IllegalStateException(ex); } return byteBuf; } private static ByteBuffer encodeAsMuchAsPossible(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final boolean endOfInput, final ByteBufferDestination destination, ByteBuffer temp) throws CharacterCodingException { 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 result.throwException(); } return temp; } private static ByteBuffer drainIfByteBufferFull(final ByteBufferDestination destination, ByteBuffer temp, final CoderResult result) { if (result.isOverflow()) { // byte buffer full // SHOULD NOT HAPPEN: // CALLER SHOULD ONLY PASS TEMP ByteBuffer LARGE ENOUGH TO ENCODE ALL CHARACTERS, // AND LOCK ON THE DESTINATION IF THIS IS NOT POSSIBLE ByteBuffer destinationBuffer = destination.getByteBuffer(); if (destinationBuffer != temp) { temp.flip(); destinationBuffer.put(temp); temp.clear(); } // destination consumes contents // and returns byte buffer with more capacity destinationBuffer = destination.drain(destinationBuffer); temp = destinationBuffer; } return temp; } private static ByteBuffer flushRemainingBytes(final CharsetEncoder charsetEncoder, final ByteBufferDestination destination, ByteBuffer temp) throws CharacterCodingException { 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 result.throwException(); } return temp; } /** * 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, start); destination.position(start + length); return length; } }