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