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.text.SimpleDateFormat;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.Date;
023import java.util.HashSet;
024import java.util.Map;
025import java.util.Set;
026
027/**
028 * Handles messages that consist of a format string containing '{}' to represent each replaceable token, and
029 * the parameters.
030 * <p/>
031 * This class was originally written for Lillith (http://mac.freshmeat.net/projects/lilith-viewer) by
032 * Joern Huxhorn where it is licensed under the LGPL. It has been relicensed here with his permission
033 * providing that this attribution remain.
034 */
035public class ParameterizedMessage implements Message {
036
037    /**
038     * Prefix for recursion.
039     */
040    public static final String RECURSION_PREFIX = "[...";
041    /**
042     * Suffix for recursion.
043     */
044    public static final String RECURSION_SUFFIX = "...]";
045
046    /**
047     * Prefix for errors.
048     */
049    public static final String ERROR_PREFIX = "[!!!";
050    /**
051     * Separator for errors.
052     */
053    public static final String ERROR_SEPARATOR = "=>";
054    /**
055     * Separator for error messages.
056     */
057    public static final String ERROR_MSG_SEPARATOR = ":";
058    /**
059     * Suffix for errors.
060     */
061    public static final String ERROR_SUFFIX = "!!!]";
062
063    private static final long serialVersionUID = -665975803997290697L;
064
065    private static final int HASHVAL = 31;
066
067    private static final char DELIM_START = '{';
068    private static final char DELIM_STOP = '}';
069    private static final char ESCAPE_CHAR = '\\';
070
071    private final String messagePattern;
072    private final String[] stringArgs;
073    private transient Object[] argArray;
074    private transient String formattedMessage;
075    private transient Throwable throwable;
076
077    /**
078     * Creates a parameterized message.
079     * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
080     * where parameters should be substituted.
081     * @param stringArgs The arguments for substitution.
082     * @param throwable A Throwable.
083     */
084    public ParameterizedMessage(final String messagePattern, final String[] stringArgs, final Throwable throwable) {
085        this.messagePattern = messagePattern;
086        this.stringArgs = stringArgs;
087        this.throwable = throwable;
088    }
089
090    /**
091     * Creates a parameterized message.
092     * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
093     * where parameters should be substituted.
094     * @param objectArgs The arguments for substitution.
095     * @param throwable A Throwable.
096     */
097    public ParameterizedMessage(final String messagePattern, final Object[] objectArgs, final Throwable throwable) {
098        this.messagePattern = messagePattern;
099        this.throwable = throwable;
100        this.stringArgs = parseArguments(objectArgs);
101    }
102
103    /**
104     * <p>This method returns a ParameterizedMessage which contains the arguments converted to String
105     * as well as an optional Throwable.</p>
106     * <p/>
107     * <p>If the last argument is a Throwable and is NOT used up by a placeholder in the message pattern it is returned
108     * in ParameterizedMessage.getThrowable() and won't be contained in the created String[].<br/>
109     * If it is used up ParameterizedMessage.getThrowable() will return null even if the last argument was a
110     * Throwable!</p>
111     *
112     * @param messagePattern the message pattern that to be checked for placeholders.
113     * @param arguments      the argument array to be converted.
114     */
115    public ParameterizedMessage(final String messagePattern, final Object[] arguments) {
116        this.messagePattern = messagePattern;
117        this.stringArgs = parseArguments(arguments);
118    }
119
120    /**
121     * Constructor with a pattern and a single parameter.
122     * @param messagePattern The message pattern.
123     * @param arg The parameter.
124     */
125    public ParameterizedMessage(final String messagePattern, final Object arg) {
126        this(messagePattern, new Object[]{arg});
127    }
128
129    /**
130     * Constructor with a pattern and two parameters.
131     * @param messagePattern The message pattern.
132     * @param arg1 The first parameter.
133     * @param arg2 The second parameter.
134     */
135    public ParameterizedMessage(final String messagePattern, final Object arg1, final Object arg2) {
136        this(messagePattern, new Object[]{arg1, arg2});
137    }
138
139    private String[] parseArguments(final Object[] arguments) {
140        if (arguments == null) {
141            return null;
142        }
143        final int argsCount = countArgumentPlaceholders(messagePattern);
144        int resultArgCount = arguments.length;
145        if (argsCount < arguments.length && throwable == null && arguments[arguments.length - 1] instanceof Throwable) {
146            throwable = (Throwable) arguments[arguments.length - 1];
147            resultArgCount--;
148        }
149        argArray = new Object[resultArgCount];
150        for (int i = 0; i < resultArgCount; ++i) {
151            argArray[i] = arguments[i];
152        }
153
154        String[] strArgs;
155        if (argsCount == 1 && throwable == null && arguments.length > 1) {
156            // special case
157            strArgs = new String[1];
158            strArgs[0] = deepToString(arguments);
159        } else {
160            strArgs = new String[resultArgCount];
161            for (int i = 0; i < strArgs.length; i++) {
162                strArgs[i] = deepToString(arguments[i]);
163            }
164        }
165        return strArgs;
166    }
167
168    /**
169     * Returns the formatted message.
170     * @return the formatted message.
171     */
172    @Override
173    public String getFormattedMessage() {
174        if (formattedMessage == null) {
175            formattedMessage = formatMessage(messagePattern, stringArgs);
176        }
177        return formattedMessage;
178    }
179
180    /**
181     * Returns the message pattern.
182     * @return the message pattern.
183     */
184    @Override
185    public String getFormat() {
186        return messagePattern;
187    }
188
189    /**
190     * Returns the message parameters.
191     * @return the message parameters.
192     */
193    @Override
194    public Object[] getParameters() {
195        if (argArray != null) {
196            return argArray;
197        }
198        return stringArgs;
199    }
200
201    /**
202     * Returns the Throwable that was given as the last argument, if any.
203     * It will not survive serialization. The Throwable exists as part of the message
204     * primarily so that it can be extracted from the end of the list of parameters
205     * and then be added to the LogEvent. As such, the Throwable in the event should
206     * not be used once the LogEvent has been constructed.
207     *
208     * @return the Throwable, if any.
209     */
210    @Override
211    public Throwable getThrowable() {
212        return throwable;
213    }
214
215    protected String formatMessage(final String msgPattern, final String[] sArgs) {
216        return format(msgPattern, sArgs);
217    }
218
219    @Override
220    public boolean equals(final Object o) {
221        if (this == o) {
222            return true;
223        }
224        if (o == null || getClass() != o.getClass()) {
225            return false;
226        }
227
228        final ParameterizedMessage that = (ParameterizedMessage) o;
229
230        if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
231            return false;
232        }
233        if (!Arrays.equals(stringArgs, that.stringArgs)) {
234            return false;
235        }
236        //if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false;
237
238        return true;
239    }
240
241    @Override
242    public int hashCode() {
243        int result = messagePattern != null ? messagePattern.hashCode() : 0;
244        result = HASHVAL * result + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0);
245        return result;
246    }
247
248    /**
249     * Replace placeholders in the given messagePattern with arguments.
250     *
251     * @param messagePattern the message pattern containing placeholders.
252     * @param arguments      the arguments to be used to replace placeholders.
253     * @return the formatted message.
254     */
255    public static String format(final String messagePattern, final Object[] arguments) {
256        if (messagePattern == null || arguments == null || arguments.length == 0) {
257            return messagePattern;
258        }
259
260        final StringBuilder result = new StringBuilder();
261        int escapeCounter = 0;
262        int currentArgument = 0;
263        for (int i = 0; i < messagePattern.length(); i++) {
264            final char curChar = messagePattern.charAt(i);
265            if (curChar == ESCAPE_CHAR) {
266                escapeCounter++;
267            } else {
268                if (curChar == DELIM_START && i < messagePattern.length() - 1
269                        && messagePattern.charAt(i + 1) == DELIM_STOP) {
270                    // write escaped escape chars
271                    final int escapedEscapes = escapeCounter / 2;
272                    for (int j = 0; j < escapedEscapes; j++) {
273                        result.append(ESCAPE_CHAR);
274                    }
275
276                    if (escapeCounter % 2 == 1) {
277                        // i.e. escaped
278                        // write escaped escape chars
279                        result.append(DELIM_START);
280                        result.append(DELIM_STOP);
281                    } else {
282                        // unescaped
283                        if (currentArgument < arguments.length) {
284                            result.append(arguments[currentArgument]);
285                        } else {
286                            result.append(DELIM_START).append(DELIM_STOP);
287                        }
288                        currentArgument++;
289                    }
290                    i++;
291                    escapeCounter = 0;
292                    continue;
293                }
294                // any other char beside ESCAPE or DELIM_START/STOP-combo
295                // write unescaped escape chars
296                if (escapeCounter > 0) {
297                    for (int j = 0; j < escapeCounter; j++) {
298                        result.append(ESCAPE_CHAR);
299                    }
300                    escapeCounter = 0;
301                }
302                result.append(curChar);
303            }
304        }
305        return result.toString();
306    }
307
308    /**
309     * Counts the number of unescaped placeholders in the given messagePattern.
310     *
311     * @param messagePattern the message pattern to be analyzed.
312     * @return the number of unescaped placeholders.
313     */
314    public static int countArgumentPlaceholders(final String messagePattern) {
315        if (messagePattern == null) {
316            return 0;
317        }
318
319        final int delim = messagePattern.indexOf(DELIM_START);
320
321        if (delim == -1) {
322            // special case, no placeholders at all.
323            return 0;
324        }
325        int result = 0;
326        boolean isEscaped = false;
327        for (int i = 0; i < messagePattern.length(); i++) {
328            final char curChar = messagePattern.charAt(i);
329            if (curChar == ESCAPE_CHAR) {
330                isEscaped = !isEscaped;
331            } else if (curChar == DELIM_START) {
332                if (!isEscaped && i < messagePattern.length() - 1 && messagePattern.charAt(i + 1) == DELIM_STOP) {
333                    result++;
334                    i++;
335                }
336                isEscaped = false;
337            } else {
338                isEscaped = false;
339            }
340        }
341        return result;
342    }
343
344    /**
345     * This method performs a deep toString of the given Object.
346     * Primitive arrays are converted using their respective Arrays.toString methods while
347     * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could
348     * contain themselves.
349     * <p/>
350     * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a
351     * behavior. They only check if the container is directly contained in itself, but not if a contained container
352     * contains the original one. Because of that, Arrays.toString(Object[]) isn't safe either.
353     * Confusing? Just read the last paragraph again and check the respective toString() implementation.
354     * <p/>
355     * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o)
356     * would produce a relatively hard-to-debug StackOverflowError.
357     * @param o The object.
358     * @return The String representation.
359     */
360    public static String deepToString(final Object o) {
361        if (o == null) {
362            return null;
363        }
364        if (o instanceof String) {
365            return (String) o;
366        }
367        final StringBuilder str = new StringBuilder();
368        final Set<String> dejaVu = new HashSet<String>(); // that's actually a neat name ;)
369        recursiveDeepToString(o, str, dejaVu);
370        return str.toString();
371    }
372
373    /**
374     * This method performs a deep toString of the given Object.
375     * Primitive arrays are converted using their respective Arrays.toString methods while
376     * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could
377     * contain themselves.
378     * <p/>
379     * dejaVu is used in case of those container types to prevent an endless recursion.
380     * <p/>
381     * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a
382     * behavior.
383     * They only check if the container is directly contained in itself, but not if a contained container contains the
384     * original one. Because of that, Arrays.toString(Object[]) isn't safe either.
385     * Confusing? Just read the last paragraph again and check the respective toString() implementation.
386     * <p/>
387     * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o)
388     * would produce a relatively hard-to-debug StackOverflowError.
389     *
390     * @param o      the Object to convert into a String
391     * @param str    the StringBuilder that o will be appended to
392     * @param dejaVu a list of container identities that were already used.
393     */
394    private static void recursiveDeepToString(final Object o, final StringBuilder str, final Set<String> dejaVu) {
395        if (o == null) {
396            str.append("null");
397            return;
398        }
399        if (o instanceof String) {
400            str.append(o);
401            return;
402        }
403
404        final Class<?> oClass = o.getClass();
405        if (oClass.isArray()) {
406            if (oClass == byte[].class) {
407                str.append(Arrays.toString((byte[]) o));
408            } else if (oClass == short[].class) {
409                str.append(Arrays.toString((short[]) o));
410            } else if (oClass == int[].class) {
411                str.append(Arrays.toString((int[]) o));
412            } else if (oClass == long[].class) {
413                str.append(Arrays.toString((long[]) o));
414            } else if (oClass == float[].class) {
415                str.append(Arrays.toString((float[]) o));
416            } else if (oClass == double[].class) {
417                str.append(Arrays.toString((double[]) o));
418            } else if (oClass == boolean[].class) {
419                str.append(Arrays.toString((boolean[]) o));
420            } else if (oClass == char[].class) {
421                str.append(Arrays.toString((char[]) o));
422            } else {
423                // special handling of container Object[]
424                final String id = identityToString(o);
425                if (dejaVu.contains(id)) {
426                    str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
427                } else {
428                    dejaVu.add(id);
429                    final Object[] oArray = (Object[]) o;
430                    str.append("[");
431                    boolean first = true;
432                    for (final Object current : oArray) {
433                        if (first) {
434                            first = false;
435                        } else {
436                            str.append(", ");
437                        }
438                        recursiveDeepToString(current, str, new HashSet<String>(dejaVu));
439                    }
440                    str.append("]");
441                }
442                //str.append(Arrays.deepToString((Object[]) o));
443            }
444        } else if (o instanceof Map) {
445            // special handling of container Map
446            final String id = identityToString(o);
447            if (dejaVu.contains(id)) {
448                str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
449            } else {
450                dejaVu.add(id);
451                final Map<?, ?> oMap = (Map<?, ?>) o;
452                str.append("{");
453                boolean isFirst = true;
454                for (final Object o1 : oMap.entrySet()) {
455                    final Map.Entry<?, ?> current = (Map.Entry<?, ?>) o1;
456                    if (isFirst) {
457                        isFirst = false;
458                    } else {
459                        str.append(", ");
460                    }
461                    final Object key = current.getKey();
462                    final Object value = current.getValue();
463                    recursiveDeepToString(key, str, new HashSet<String>(dejaVu));
464                    str.append("=");
465                    recursiveDeepToString(value, str, new HashSet<String>(dejaVu));
466                }
467                str.append("}");
468            }
469        } else if (o instanceof Collection) {
470            // special handling of container Collection
471            final String id = identityToString(o);
472            if (dejaVu.contains(id)) {
473                str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
474            } else {
475                dejaVu.add(id);
476                final Collection<?> oCol = (Collection<?>) o;
477                str.append("[");
478                boolean isFirst = true;
479                for (final Object anOCol : oCol) {
480                    if (isFirst) {
481                        isFirst = false;
482                    } else {
483                        str.append(", ");
484                    }
485                    recursiveDeepToString(anOCol, str, new HashSet<String>(dejaVu));
486                }
487                str.append("]");
488            }
489        } else if (o instanceof Date) {
490            final Date date = (Date) o;
491            final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
492            // I'll leave it like this for the moment... this could probably be optimized using ThreadLocal...
493            str.append(format.format(date));
494        } else {
495            // it's just some other Object, we can only use toString().
496            try {
497                str.append(o.toString());
498            } catch (final Throwable t) {
499                str.append(ERROR_PREFIX);
500                str.append(identityToString(o));
501                str.append(ERROR_SEPARATOR);
502                final String msg = t.getMessage();
503                final String className = t.getClass().getName();
504                str.append(className);
505                if (!className.equals(msg)) {
506                    str.append(ERROR_MSG_SEPARATOR);
507                    str.append(msg);
508                }
509                str.append(ERROR_SUFFIX);
510            }
511        }
512    }
513
514    /**
515     * This method returns the same as if Object.toString() would not have been
516     * overridden in obj.
517     * <p/>
518     * Note that this isn't 100% secure as collisions can always happen with hash codes.
519     * <p/>
520     * Copied from Object.hashCode():
521     * As much as is reasonably practical, the hashCode method defined by
522     * class <tt>Object</tt> does return distinct integers for distinct
523     * objects. (This is typically implemented by converting the internal
524     * address of the object into an integer, but this implementation
525     * technique is not required by the
526     * Java<font size="-2"><sup>TM</sup></font>
527     * programming language.)
528     *
529     * @param obj the Object that is to be converted into an identity string.
530     * @return the identity string as also defined in Object.toString()
531     */
532    public static String identityToString(final Object obj) {
533        if (obj == null) {
534            return null;
535        }
536        return obj.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(obj));
537    }
538
539    @Override
540    public String toString() {
541        return "ParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" +
542            Arrays.toString(stringArgs) + ", throwable=" + throwable + "]";
543    }
544}