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 org.apache.logging.log4j.core.util.Constants;
020import org.apache.logging.log4j.status.StatusLogger;
021
022import java.nio.CharBuffer;
023import java.nio.charset.Charset;
024import java.nio.charset.CharsetEncoder;
025import java.nio.charset.CodingErrorAction;
026import java.util.Objects;
027
028/**
029 * Encoder for StringBuilders that locks on the ByteBufferDestination.
030 */
031public class LockingStringBuilderEncoder implements Encoder<StringBuilder> {
032
033    private final Charset charset;
034    private final CharsetEncoder charsetEncoder;
035    private final CharBuffer cachedCharBuffer;
036
037    public LockingStringBuilderEncoder(final Charset charset) {
038        this(charset, Constants.ENCODER_CHAR_BUFFER_SIZE);
039    }
040
041    public LockingStringBuilderEncoder(final Charset charset, final int charBufferSize) {
042        this.charset = Objects.requireNonNull(charset, "charset");
043        this.charsetEncoder = charset.newEncoder().onMalformedInput(CodingErrorAction.REPLACE)
044                .onUnmappableCharacter(CodingErrorAction.REPLACE);
045        this.cachedCharBuffer = CharBuffer.wrap(new char[charBufferSize]);
046    }
047
048    private CharBuffer getCharBuffer() {
049        return cachedCharBuffer;
050    }
051
052    @Override
053    public void encode(final StringBuilder source, final ByteBufferDestination destination) {
054        try {
055            // This synchronized is needed to be able to call destination.getByteBuffer()
056            synchronized (destination) {
057                TextEncoderHelper.encodeText(charsetEncoder, cachedCharBuffer, destination.getByteBuffer(), source,
058                    destination);
059            }
060        } catch (final Exception ex) {
061            logEncodeTextException(ex, source, destination);
062            TextEncoderHelper.encodeTextFallBack(charset, source, destination);
063        }
064
065    }
066
067    private void logEncodeTextException(final Exception ex, final StringBuilder text,
068                                        final ByteBufferDestination destination) {
069        StatusLogger.getLogger().error("Recovering from LockingStringBuilderEncoder.encode('{}') error", text, ex);
070    }
071}