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