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.StringBuilderFormattable;
22  
23  /**
24   * Handles messages that consist of a format string containing '{}' to represent each replaceable token, and
25   * the parameters.
26   * <p>
27   * This class was originally written for <a href="http://lilithapp.com/">Lilith</a> by Joern Huxhorn where it is
28   * licensed under the LGPL. It has been relicensed here with his permission providing that this attribution remain.
29   * </p>
30   */
31  public class ParameterizedMessage implements Message, StringBuilderFormattable {
32  
33      // Should this be configurable?
34      private static final int DEFAULT_STRING_BUILDER_SIZE = 255;
35  
36      /**
37       * Prefix for recursion.
38       */
39      public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX;
40      /**
41       * Suffix for recursion.
42       */
43      public static final String RECURSION_SUFFIX = ParameterFormatter.RECURSION_SUFFIX;
44  
45      /**
46       * Prefix for errors.
47       */
48      public static final String ERROR_PREFIX = ParameterFormatter.ERROR_PREFIX;
49  
50      /**
51       * Separator for errors.
52       */
53      public static final String ERROR_SEPARATOR = ParameterFormatter.ERROR_SEPARATOR;
54  
55      /**
56       * Separator for error messages.
57       */
58      public static final String ERROR_MSG_SEPARATOR = ParameterFormatter.ERROR_MSG_SEPARATOR;
59  
60      /**
61       * Suffix for errors.
62       */
63      public static final String ERROR_SUFFIX = ParameterFormatter.ERROR_SUFFIX;
64  
65      private static final long serialVersionUID = -665975803997290697L;
66  
67      private static final int HASHVAL = 31;
68  
69      // storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay
70      private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
71  
72      private String messagePattern;
73      private transient Object[] argArray;
74  
75      private String formattedMessage;
76      private transient Throwable throwable;
77      private int[] indices;
78      private int usedCount;
79  
80      /**
81       * Creates a parameterized message.
82       * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
83       * where parameters should be substituted.
84       * @param arguments The arguments for substitution.
85       * @param throwable A Throwable.
86       * @deprecated Use constructor ParameterizedMessage(String, Object[], Throwable) instead
87       */
88      @Deprecated
89      public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) {
90          this.argArray = arguments;
91          this.throwable = throwable;
92          init(messagePattern);
93      }
94  
95      /**
96       * Creates a parameterized message.
97       * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
98       * where parameters should be substituted.
99       * @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         final int len = Math.max(1, messagePattern == null ? 0 : messagePattern.length() >> 1); // divide by 2
146         this.indices = new int[len]; // LOG4J2-1542 ensure non-zero array length
147         final int placeholders = ParameterFormatter.countArgumentPlaceholders2(messagePattern, indices);
148         initThrowable(argArray, placeholders);
149         this.usedCount = Math.min(placeholders, argArray == null ? 0 : argArray.length);
150     }
151 
152     private void initThrowable(final Object[] params, final int usedParams) {
153         if (params != null) {
154             final int argCount = params.length;
155             if (usedParams < argCount && this.throwable == null && params[argCount - 1] instanceof Throwable) {
156                 this.throwable = (Throwable) params[argCount - 1];
157             }
158         }
159     }
160 
161     /**
162      * Returns the message pattern.
163      * @return the message pattern.
164      */
165     @Override
166     public String getFormat() {
167         return messagePattern;
168     }
169 
170     /**
171      * Returns the message parameters.
172      * @return the message parameters.
173      */
174     @Override
175     public Object[] getParameters() {
176         return argArray;
177     }
178 
179     /**
180      * Returns the Throwable that was given as the last argument, if any.
181      * It will not survive serialization. The Throwable exists as part of the message
182      * primarily so that it can be extracted from the end of the list of parameters
183      * and then be added to the LogEvent. As such, the Throwable in the event should
184      * not be used once the LogEvent has been constructed.
185      *
186      * @return the Throwable, if any.
187      */
188     @Override
189     public Throwable getThrowable() {
190         return throwable;
191     }
192 
193     /**
194      * Returns the formatted message.
195      * @return the formatted message.
196      */
197     @Override
198     public String getFormattedMessage() {
199         if (formattedMessage == null) {
200             final StringBuilder buffer = getThreadLocalStringBuilder();
201             formatTo(buffer);
202             formattedMessage = buffer.toString();
203         }
204         return formattedMessage;
205     }
206 
207     private static StringBuilder getThreadLocalStringBuilder() {
208         StringBuilder buffer = threadLocalStringBuilder.get();
209         if (buffer == null) {
210             buffer = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
211             threadLocalStringBuilder.set(buffer);
212         }
213         buffer.setLength(0);
214         return buffer;
215     }
216 
217     @Override
218     public void formatTo(final StringBuilder buffer) {
219         if (formattedMessage != null) {
220             buffer.append(formattedMessage);
221         } else {
222             if (indices[0] < 0) {
223                 ParameterFormatter.formatMessage(buffer, messagePattern, argArray, usedCount);
224             } else {
225                 ParameterFormatter.formatMessage2(buffer, messagePattern, argArray, usedCount, indices);
226             }
227         }
228     }
229 
230     /**
231      * Replace placeholders in the given messagePattern with arguments.
232      *
233      * @param messagePattern the message pattern containing placeholders.
234      * @param arguments      the arguments to be used to replace placeholders.
235      * @return the formatted message.
236      */
237     public static String format(final String messagePattern, final Object[] arguments) {
238         return ParameterFormatter.format(messagePattern, arguments);
239     }
240 
241     @Override
242     public boolean equals(final Object o) {
243         if (this == o) {
244             return true;
245         }
246         if (o == null || getClass() != o.getClass()) {
247             return false;
248         }
249 
250         final ParameterizedMessage that = (ParameterizedMessage) o;
251 
252         if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
253             return false;
254         }
255         if (!Arrays.equals(this.argArray, that.argArray)) {
256             return false;
257         }
258         //if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false;
259 
260         return true;
261     }
262 
263     @Override
264     public int hashCode() {
265         int result = messagePattern != null ? messagePattern.hashCode() : 0;
266         result = HASHVAL * result + (argArray != null ? Arrays.hashCode(argArray) : 0);
267         return result;
268     }
269 
270     /**
271      * Counts the number of unescaped placeholders in the given messagePattern.
272      *
273      * @param messagePattern the message pattern to be analyzed.
274      * @return the number of unescaped placeholders.
275      */
276     public static int countArgumentPlaceholders(final String messagePattern) {
277         return ParameterFormatter.countArgumentPlaceholders(messagePattern);
278     }
279 
280     /**
281      * This method performs a deep toString of the given Object.
282      * Primitive arrays are converted using their respective Arrays.toString methods while
283      * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could
284      * contain themselves.
285      * <p>
286      * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a
287      * behavior. They only check if the container is directly contained in itself, but not if a contained container
288      * contains the original one. Because of that, Arrays.toString(Object[]) isn't safe either.
289      * Confusing? Just read the last paragraph again and check the respective toString() implementation.
290      * </p>
291      * <p>
292      * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o)
293      * would produce a relatively hard-to-debug StackOverflowError.
294      * </p>
295      * @param o The object.
296      * @return The String representation.
297      */
298     public static String deepToString(final Object o) {
299         return ParameterFormatter.deepToString(o);
300     }
301 
302     /**
303      * This method returns the same as if Object.toString() would not have been
304      * overridden in obj.
305      * <p>
306      * Note that this isn't 100% secure as collisions can always happen with hash codes.
307      * </p>
308      * <p>
309      * Copied from Object.hashCode():
310      * </p>
311      * <blockquote>
312      * As much as is reasonably practical, the hashCode method defined by
313      * class {@code Object} does return distinct integers for distinct
314      * objects. (This is typically implemented by converting the internal
315      * address of the object into an integer, but this implementation
316      * technique is not required by the Java&#8482; programming language.)
317      * </blockquote>
318      *
319      * @param obj the Object that is to be converted into an identity string.
320      * @return the identity string as also defined in Object.toString()
321      */
322     public static String identityToString(final Object obj) {
323         return ParameterFormatter.identityToString(obj);
324     }
325 
326     @Override
327     public String toString() {
328         return "ParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" +
329                 Arrays.toString(argArray) + ", throwable=" + throwable + ']';
330     }
331 }