001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.layout; 018 019import java.nio.ByteBuffer; 020import java.nio.CharBuffer; 021import java.nio.charset.CharacterCodingException; 022import java.nio.charset.Charset; 023import java.nio.charset.CharsetEncoder; 024import java.nio.charset.CoderResult; 025 026/** 027 * Helper class to encode text to binary data without allocating temporary objects. 028 * 029 * @since 2.6 030 */ 031public class TextEncoderHelper { 032 033 private TextEncoderHelper() { 034 } 035 036 static void encodeTextFallBack(final Charset charset, final StringBuilder text, 037 final ByteBufferDestination destination) { 038 final byte[] bytes = text.toString().getBytes(charset); 039 synchronized (destination) { 040 ByteBuffer buffer = destination.getByteBuffer(); 041 int offset = 0; 042 do { 043 final int length = Math.min(bytes.length - offset, buffer.remaining()); 044 buffer.put(bytes, offset, length); 045 offset += length; 046 if (offset < bytes.length) { 047 buffer = destination.drain(buffer); 048 } 049 } while (offset < bytes.length); 050 } 051 } 052 053 static void encodeTextWithCopy(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer temp, 054 final StringBuilder text, final ByteBufferDestination destination) { 055 056 encodeText(charsetEncoder, charBuf, temp, text, destination); 057 copyDataToDestination(temp, destination); 058 } 059 060 private static void copyDataToDestination(final ByteBuffer temp, final ByteBufferDestination destination) { 061 synchronized (destination) { 062 ByteBuffer destinationBuffer = destination.getByteBuffer(); 063 if (destinationBuffer != temp) { // still need to write to the destination 064 temp.flip(); 065 if (temp.remaining() > destinationBuffer.remaining()) { 066 destinationBuffer = destination.drain(destinationBuffer); 067 } 068 destinationBuffer.put(temp); 069 temp.clear(); 070 } 071 } 072 } 073 074 static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer byteBuf, 075 final StringBuilder text, final ByteBufferDestination destination) { 076 charsetEncoder.reset(); 077 ByteBuffer temp = byteBuf; // may be the destination's buffer or a temporary buffer 078 int start = 0; 079 int todoChars = text.length(); 080 boolean endOfInput = true; 081 do { 082 charBuf.clear(); // reset character buffer position to zero, limit to capacity 083 int copied = copy(text, start, charBuf); 084 start += copied; 085 todoChars -= copied; 086 endOfInput = todoChars <= 0; 087 088 charBuf.flip(); // prepare for reading: set limit to position, position to zero 089 temp = encode(charsetEncoder, charBuf, endOfInput, destination, temp); 090 } while (!endOfInput); 091 } 092 093 /** 094 * For testing purposes only. 095 */ 096 @Deprecated 097 public static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, 098 final ByteBufferDestination destination) { 099 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(ByteBufferDestination destination, ByteBuffer temp, 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}