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