View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.message;
18  
19  import java.util.Arrays;
20  
21  import org.apache.logging.log4j.util.Constants;
22  import org.apache.logging.log4j.util.StringBuilderFormattable;
23  import org.apache.logging.log4j.util.StringBuilders;
24  
25  /**
26   * Handles messages that consist of a format string containing '{}' to represent each replaceable token, and
27   * the parameters.
28   * <p>
29   * This class was originally written for <a href="http://lilithapp.com/">Lilith</a> by Joern Huxhorn where it is
30   * licensed under the LGPL. It has been relicensed here with his permission providing that this attribution remain.
31   * </p>
32   */
33  public class ParameterizedMessage implements Message, StringBuilderFormattable {
34  
35      // Should this be configurable?
36      private static final int DEFAULT_STRING_BUILDER_SIZE = 255;
37  
38      /**
39       * Prefix for recursion.
40       */
41      public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX;
42      /**
43       * Suffix for recursion.
44       */
45      public static final String RECURSION_SUFFIX = ParameterFormatter.RECURSION_SUFFIX;
46  
47      /**
48       * Prefix for errors.
49       */
50      public static final String ERROR_PREFIX = ParameterFormatter.ERROR_PREFIX;
51  
52      /**
53       * Separator for errors.
54       */
55      public static final String ERROR_SEPARATOR = ParameterFormatter.ERROR_SEPARATOR;
56  
57      /**
58       * Separator for error messages.
59       */
60      public static final String ERROR_MSG_SEPARATOR = ParameterFormatter.ERROR_MSG_SEPARATOR;
61  
62      /**
63       * Suffix for errors.
64       */
65      public static final String ERROR_SUFFIX = ParameterFormatter.ERROR_SUFFIX;
66  
67      private static final long serialVersionUID = -665975803997290697L;
68  
69      private static final int HASHVAL = 31;
70  
71      // storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay
72      private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
73  
74      private String messagePattern;
75      private transient Object[] argArray;
76  
77      private String formattedMessage;
78      private transient Throwable throwable;
79      private int[] indices;
80      private int usedCount;
81  
82      /**
83       * Creates a parameterized message.
84       * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
85       * where parameters should be substituted.
86       * @param arguments The arguments for substitution.
87       * @param throwable A Throwable.
88       * @deprecated Use constructor ParameterizedMessage(String, Object[], Throwable) instead
89       */
90      @Deprecated
91      public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) {
92          this.argArray = arguments;
93          this.throwable = throwable;
94          init(messagePattern);
95      }
96  
97      /**
98       * Creates a parameterized message.
99       * @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 }