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.message;
018
019import java.util.Arrays;
020
021import org.apache.logging.log4j.util.Constants;
022import org.apache.logging.log4j.util.PerformanceSensitive;
023import org.apache.logging.log4j.util.StringBuilders;
024
025/**
026 * Reusable parameterized message. This message is mutable and is not safe to be accessed or modified by multiple
027 * threads concurrently.
028 *
029 * @see ParameterizedMessage
030 * @since 2.6
031 */
032@PerformanceSensitive("allocation")
033public class ReusableParameterizedMessage implements ReusableMessage, ParameterVisitable, Clearable {
034
035    private static final int MIN_BUILDER_SIZE = 512;
036    private static final int MAX_PARMS = 10;
037    private static final long serialVersionUID = 7800075879295123856L;
038    private transient ThreadLocal<StringBuilder> buffer; // non-static: LOG4J2-1583
039
040    private String messagePattern;
041    private int argCount;
042    private int usedCount;
043    private final int[] indices = new int[256];
044    private transient Object[] varargs;
045    private transient Object[] params = new Object[MAX_PARMS];
046    private transient Throwable throwable;
047    transient boolean reserved = false; // LOG4J2-1583 prevent scrambled logs with nested logging calls
048
049    /**
050     * Creates a reusable message.
051     */
052    public ReusableParameterizedMessage() {
053    }
054
055    private Object[] getTrimmedParams() {
056        return varargs == null ? Arrays.copyOf(params, argCount) : varargs;
057    }
058
059    private Object[] getParams() {
060        return varargs == null ? params : varargs;
061    }
062
063    // see interface javadoc
064    @Override
065    public Object[] swapParameters(final Object[] emptyReplacement) {
066        Object[] result;
067        if (varargs == null) {
068            result = params;
069            if (emptyReplacement.length >= MAX_PARMS) {
070                params = emptyReplacement;
071            } else {
072                // Bad replacement! Too small, may blow up future 10-arg messages.
073                if (argCount <= emptyReplacement.length) {
074                    // copy params into the specified replacement array and return that
075                    System.arraycopy(params, 0, emptyReplacement, 0, argCount);
076                    result = emptyReplacement;
077                } else {
078                    // replacement array is too small for current content and future content: discard it
079                    params = new Object[MAX_PARMS];
080                }
081            }
082        } else {
083            // The returned array will be reused by the caller in future swapParameter() calls.
084            // Therefore we want to avoid returning arrays with less than 10 elements.
085            // If the vararg array is less than 10 params we just copy its content into the specified array
086            // and return it. This helps the caller to retain a reusable array of at least 10 elements.
087            // NOTE: LOG4J2-1688 unearthed the use case that an application array (not a varargs array) is passed
088            // as the argument array. This array should not be modified, so it cannot be passed to the caller
089            // who will at some point null out the elements in the array).
090            if (argCount <= emptyReplacement.length) {
091                result = emptyReplacement;
092            } else {
093                result = new Object[argCount]; // LOG4J2-1688
094            }
095            // copy params into the specified replacement array and return that
096            System.arraycopy(varargs, 0, result, 0, argCount);
097        }
098        return result;
099    }
100
101    // see interface javadoc
102    @Override
103    public short getParameterCount() {
104        return (short) argCount;
105    }
106
107    @Override
108    public <S> void forEachParameter(ParameterConsumer<S> action, S state) {
109        Object[] parameters = getParams();
110        for (short i = 0; i < argCount; i++) {
111            action.accept(parameters[i], i, state);
112        }
113    }
114
115    @Override
116    public Message memento() {
117        return new ParameterizedMessage(messagePattern, getTrimmedParams());
118    }
119
120    private void init(final String messagePattern, final int argCount, final Object[] paramArray) {
121        this.varargs = null;
122        this.messagePattern = messagePattern;
123        this.argCount = argCount;
124        final int placeholderCount = count(messagePattern, indices);
125        initThrowable(paramArray, argCount, placeholderCount);
126        this.usedCount = Math.min(placeholderCount, argCount);
127    }
128
129    private static int count(final String messagePattern, final int[] indices) {
130        try {
131            // try the fast path first
132            return ParameterFormatter.countArgumentPlaceholders2(messagePattern, indices);
133        } catch (final Exception ex) { // fallback if more than int[] length (256) parameter placeholders
134            return ParameterFormatter.countArgumentPlaceholders(messagePattern);
135        }
136    }
137
138    private void initThrowable(final Object[] params, final int argCount, final int usedParams) {
139        if (usedParams < argCount && params[argCount - 1] instanceof Throwable) {
140            this.throwable = (Throwable) params[argCount - 1];
141        } else {
142            this.throwable = null;
143        }
144    }
145
146    ReusableParameterizedMessage set(final String messagePattern, final Object... arguments) {
147        init(messagePattern, arguments == null ? 0 : arguments.length, arguments);
148        varargs = arguments;
149        return this;
150    }
151
152    ReusableParameterizedMessage set(final String messagePattern, final Object p0) {
153        params[0] = p0;
154        init(messagePattern, 1, params);
155        return this;
156    }
157
158    ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1) {
159        params[0] = p0;
160        params[1] = p1;
161        init(messagePattern, 2, params);
162        return this;
163    }
164
165    ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2) {
166        params[0] = p0;
167        params[1] = p1;
168        params[2] = p2;
169        init(messagePattern, 3, params);
170        return this;
171    }
172
173    ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3) {
174        params[0] = p0;
175        params[1] = p1;
176        params[2] = p2;
177        params[3] = p3;
178        init(messagePattern, 4, params);
179        return this;
180    }
181
182    ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) {
183        params[0] = p0;
184        params[1] = p1;
185        params[2] = p2;
186        params[3] = p3;
187        params[4] = p4;
188        init(messagePattern, 5, params);
189        return this;
190    }
191
192    ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) {
193        params[0] = p0;
194        params[1] = p1;
195        params[2] = p2;
196        params[3] = p3;
197        params[4] = p4;
198        params[5] = p5;
199        init(messagePattern, 6, params);
200        return this;
201    }
202
203    ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
204            final Object p6) {
205        params[0] = p0;
206        params[1] = p1;
207        params[2] = p2;
208        params[3] = p3;
209        params[4] = p4;
210        params[5] = p5;
211        params[6] = p6;
212        init(messagePattern, 7, params);
213        return this;
214    }
215
216    ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
217            final Object p6, final Object p7) {
218        params[0] = p0;
219        params[1] = p1;
220        params[2] = p2;
221        params[3] = p3;
222        params[4] = p4;
223        params[5] = p5;
224        params[6] = p6;
225        params[7] = p7;
226        init(messagePattern, 8, params);
227        return this;
228    }
229
230    ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
231            final Object p6, final Object p7, final Object p8) {
232        params[0] = p0;
233        params[1] = p1;
234        params[2] = p2;
235        params[3] = p3;
236        params[4] = p4;
237        params[5] = p5;
238        params[6] = p6;
239        params[7] = p7;
240        params[8] = p8;
241        init(messagePattern, 9, params);
242        return this;
243    }
244
245    ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
246            final Object p6, final Object p7, final Object p8, final Object p9) {
247        params[0] = p0;
248        params[1] = p1;
249        params[2] = p2;
250        params[3] = p3;
251        params[4] = p4;
252        params[5] = p5;
253        params[6] = p6;
254        params[7] = p7;
255        params[8] = p8;
256        params[9] = p9;
257        init(messagePattern, 10, params);
258        return this;
259    }
260
261    /**
262     * Returns the message pattern.
263     * @return the message pattern.
264     */
265    @Override
266    public String getFormat() {
267        return messagePattern;
268    }
269
270    /**
271     * Returns the message parameters.
272     * @return the message parameters.
273     */
274    @Override
275    public Object[] getParameters() {
276        return getTrimmedParams();
277    }
278
279    /**
280     * Returns the Throwable that was given as the last argument, if any.
281     * It will not survive serialization. The Throwable exists as part of the message
282     * primarily so that it can be extracted from the end of the list of parameters
283     * and then be added to the LogEvent. As such, the Throwable in the event should
284     * not be used once the LogEvent has been constructed.
285     *
286     * @return the Throwable, if any.
287     */
288    @Override
289    public Throwable getThrowable() {
290        return throwable;
291    }
292
293    /**
294     * Returns the formatted message.
295     * @return the formatted message.
296     */
297    @Override
298    public String getFormattedMessage() {
299        final StringBuilder sb = getBuffer();
300        formatTo(sb);
301        final String result = sb.toString();
302        StringBuilders.trimToMaxSize(sb, Constants.MAX_REUSABLE_MESSAGE_SIZE);
303        return result;
304    }
305
306    private StringBuilder getBuffer() {
307        if (buffer == null) {
308            buffer = new ThreadLocal<>();
309        }
310        StringBuilder result = buffer.get();
311        if (result == null) {
312            final int currentPatternLength = messagePattern == null ? 0 : messagePattern.length();
313            result = new StringBuilder(Math.max(MIN_BUILDER_SIZE, currentPatternLength * 2));
314            buffer.set(result);
315        }
316        result.setLength(0);
317        return result;
318    }
319
320    @Override
321    public void formatTo(final StringBuilder builder) {
322        if (indices[0] < 0) {
323            ParameterFormatter.formatMessage(builder, messagePattern, getParams(), argCount);
324        } else {
325            ParameterFormatter.formatMessage2(builder, messagePattern, getParams(), usedCount, indices);
326        }
327    }
328
329    /**
330     * Sets the reserved flag to true and returns this object.
331     * @return this object
332     * @since 2.7
333     */
334    ReusableParameterizedMessage reserve() {
335        reserved = true;
336        return this;
337    }
338
339    @Override
340    public String toString() {
341        return "ReusableParameterizedMessage[messagePattern=" + getFormat() + ", stringArgs=" +
342                Arrays.toString(getParameters()) + ", throwable=" + getThrowable() + ']';
343    }
344
345    @Override
346    public void clear() { // LOG4J2-1583
347        // This method does not clear parameter values, those are expected to be swapped to a
348        // reusable message, which is responsible for clearing references.
349        reserved = false;
350        varargs = null;
351        messagePattern = null;
352        throwable = null;
353    }
354}