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.StringBuilderFormattable;
023import org.apache.logging.log4j.util.StringBuilders;
024
025/**
026 * Handles messages that consist of a format string containing '{}' to represent each replaceable token, and
027 * the parameters.
028 * <p>
029 * This class was originally written for <a href="http://lilithapp.com/">Lilith</a> by Joern Huxhorn where it is
030 * licensed under the LGPL. It has been relicensed here with his permission providing that this attribution remain.
031 * </p>
032 */
033public class ParameterizedMessage implements Message, StringBuilderFormattable {
034
035    // Should this be configurable?
036    private static final int DEFAULT_STRING_BUILDER_SIZE = 255;
037
038    /**
039     * Prefix for recursion.
040     */
041    public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX;
042    /**
043     * Suffix for recursion.
044     */
045    public static final String RECURSION_SUFFIX = ParameterFormatter.RECURSION_SUFFIX;
046
047    /**
048     * Prefix for errors.
049     */
050    public static final String ERROR_PREFIX = ParameterFormatter.ERROR_PREFIX;
051
052    /**
053     * Separator for errors.
054     */
055    public static final String ERROR_SEPARATOR = ParameterFormatter.ERROR_SEPARATOR;
056
057    /**
058     * Separator for error messages.
059     */
060    public static final String ERROR_MSG_SEPARATOR = ParameterFormatter.ERROR_MSG_SEPARATOR;
061
062    /**
063     * Suffix for errors.
064     */
065    public static final String ERROR_SUFFIX = ParameterFormatter.ERROR_SUFFIX;
066
067    private static final long serialVersionUID = -665975803997290697L;
068
069    private static final int HASHVAL = 31;
070
071    // storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay
072    private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
073
074    private String messagePattern;
075    private transient Object[] argArray;
076
077    private String formattedMessage;
078    private transient Throwable throwable;
079    private int[] indices;
080    private int usedCount;
081
082    /**
083     * Creates a parameterized message.
084     * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
085     * where parameters should be substituted.
086     * @param arguments The arguments for substitution.
087     * @param throwable A Throwable.
088     * @deprecated Use constructor ParameterizedMessage(String, Object[], Throwable) instead
089     */
090    @Deprecated
091    public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) {
092        this.argArray = arguments;
093        this.throwable = throwable;
094        init(messagePattern);
095    }
096
097    /**
098     * Creates a parameterized message.
099     * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
100     * where parameters should be substituted.
101     * @param arguments The arguments for substitution.
102     * @param throwable A Throwable.
103     */
104    public ParameterizedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
105        this.argArray = arguments;
106        this.throwable = throwable;
107        init(messagePattern);
108    }
109
110    /**
111     * Constructs a ParameterizedMessage which contains the arguments converted to String as well as an optional
112     * Throwable.
113     *
114     * <p>If the last argument is a Throwable and is NOT used up by a placeholder in the message pattern it is returned
115     * in {@link #getThrowable()} and won't be contained in the created String[].
116     * If it is used up {@link #getThrowable()} will return null even if the last argument was a Throwable!</p>
117     *
118     * @param messagePattern the message pattern that to be checked for placeholders.
119     * @param arguments      the argument array to be converted.
120     */
121    public ParameterizedMessage(final String messagePattern, final Object... arguments) {
122        this.argArray = arguments;
123        init(messagePattern);
124    }
125
126    /**
127     * Constructor with a pattern and a single parameter.
128     * @param messagePattern The message pattern.
129     * @param arg The parameter.
130     */
131    public ParameterizedMessage(final String messagePattern, final Object arg) {
132        this(messagePattern, new Object[]{arg});
133    }
134
135    /**
136     * Constructor with a pattern and two parameters.
137     * @param messagePattern The message pattern.
138     * @param arg0 The first parameter.
139     * @param arg1 The second parameter.
140     */
141    public ParameterizedMessage(final String messagePattern, final Object arg0, final Object arg1) {
142        this(messagePattern, new Object[]{arg0, arg1});
143    }
144
145    private void init(final String messagePattern) {
146        this.messagePattern = messagePattern;
147        final int len = Math.max(1, messagePattern == null ? 0 : messagePattern.length() >> 1); // divide by 2
148        this.indices = new int[len]; // LOG4J2-1542 ensure non-zero array length
149        final int placeholders = ParameterFormatter.countArgumentPlaceholders2(messagePattern, indices);
150        initThrowable(argArray, placeholders);
151        this.usedCount = Math.min(placeholders, argArray == null ? 0 : argArray.length);
152    }
153
154    private void initThrowable(final Object[] params, final int usedParams) {
155        if (params != null) {
156            final int argCount = params.length;
157            if (usedParams < argCount && this.throwable == null && params[argCount - 1] instanceof Throwable) {
158                this.throwable = (Throwable) params[argCount - 1];
159            }
160        }
161    }
162
163    /**
164     * Returns the message pattern.
165     * @return the message pattern.
166     */
167    @Override
168    public String getFormat() {
169        return messagePattern;
170    }
171
172    /**
173     * Returns the message parameters.
174     * @return the message parameters.
175     */
176    @Override
177    public Object[] getParameters() {
178        return argArray;
179    }
180
181    /**
182     * Returns the Throwable that was given as the last argument, if any.
183     * It will not survive serialization. The Throwable exists as part of the message
184     * primarily so that it can be extracted from the end of the list of parameters
185     * and then be added to the LogEvent. As such, the Throwable in the event should
186     * not be used once the LogEvent has been constructed.
187     *
188     * @return the Throwable, if any.
189     */
190    @Override
191    public Throwable getThrowable() {
192        return throwable;
193    }
194
195    /**
196     * Returns the formatted message.
197     * @return the formatted message.
198     */
199    @Override
200    public String getFormattedMessage() {
201        if (formattedMessage == null) {
202            final StringBuilder buffer = getThreadLocalStringBuilder();
203            formatTo(buffer);
204            formattedMessage = buffer.toString();
205            StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE);
206        }
207        return formattedMessage;
208    }
209
210    private static StringBuilder getThreadLocalStringBuilder() {
211        StringBuilder buffer = threadLocalStringBuilder.get();
212        if (buffer == null) {
213            buffer = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
214            threadLocalStringBuilder.set(buffer);
215        }
216        buffer.setLength(0);
217        return buffer;
218    }
219
220    @Override
221    public void formatTo(final StringBuilder buffer) {
222        if (formattedMessage != null) {
223            buffer.append(formattedMessage);
224        } else {
225            if (indices[0] < 0) {
226                ParameterFormatter.formatMessage(buffer, messagePattern, argArray, usedCount);
227            } else {
228                ParameterFormatter.formatMessage2(buffer, messagePattern, argArray, usedCount, indices);
229            }
230        }
231    }
232
233    /**
234     * Replace placeholders in the given messagePattern with arguments.
235     *
236     * @param messagePattern the message pattern containing placeholders.
237     * @param arguments      the arguments to be used to replace placeholders.
238     * @return the formatted message.
239     */
240    public static String format(final String messagePattern, final Object[] arguments) {
241        return ParameterFormatter.format(messagePattern, arguments);
242    }
243
244    @Override
245    public boolean equals(final Object o) {
246        if (this == o) {
247            return true;
248        }
249        if (o == null || getClass() != o.getClass()) {
250            return false;
251        }
252
253        final ParameterizedMessage that = (ParameterizedMessage) o;
254
255        if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
256            return false;
257        }
258        if (!Arrays.equals(this.argArray, that.argArray)) {
259            return false;
260        }
261        //if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false;
262
263        return true;
264    }
265
266    @Override
267    public int hashCode() {
268        int result = messagePattern != null ? messagePattern.hashCode() : 0;
269        result = HASHVAL * result + (argArray != null ? Arrays.hashCode(argArray) : 0);
270        return result;
271    }
272
273    /**
274     * Counts the number of unescaped placeholders in the given messagePattern.
275     *
276     * @param messagePattern the message pattern to be analyzed.
277     * @return the number of unescaped placeholders.
278     */
279    public static int countArgumentPlaceholders(final String messagePattern) {
280        return ParameterFormatter.countArgumentPlaceholders(messagePattern);
281    }
282
283    /**
284     * This method performs a deep toString of the given Object.
285     * Primitive arrays are converted using their respective Arrays.toString methods while
286     * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could
287     * contain themselves.
288     * <p>
289     * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a
290     * behavior. They only check if the container is directly contained in itself, but not if a contained container
291     * contains the original one. Because of that, Arrays.toString(Object[]) isn't safe either.
292     * Confusing? Just read the last paragraph again and check the respective toString() implementation.
293     * </p>
294     * <p>
295     * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o)
296     * would produce a relatively hard-to-debug StackOverflowError.
297     * </p>
298     * @param o The object.
299     * @return The String representation.
300     */
301    public static String deepToString(final Object o) {
302        return ParameterFormatter.deepToString(o);
303    }
304
305    /**
306     * This method returns the same as if Object.toString() would not have been
307     * overridden in obj.
308     * <p>
309     * Note that this isn't 100% secure as collisions can always happen with hash codes.
310     * </p>
311     * <p>
312     * Copied from Object.hashCode():
313     * </p>
314     * <blockquote>
315     * As much as is reasonably practical, the hashCode method defined by
316     * class {@code Object} does return distinct integers for distinct
317     * objects. (This is typically implemented by converting the internal
318     * address of the object into an integer, but this implementation
319     * technique is not required by the Java&#8482; programming language.)
320     * </blockquote>
321     *
322     * @param obj the Object that is to be converted into an identity string.
323     * @return the identity string as also defined in Object.toString()
324     */
325    public static String identityToString(final Object obj) {
326        return ParameterFormatter.identityToString(obj);
327    }
328
329    @Override
330    public String toString() {
331        return "ParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" +
332                Arrays.toString(argArray) + ", throwable=" + throwable + ']';
333    }
334}