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