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       * Prefix for recursion.
34       */
35      public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX;
36      /**
37       * Suffix for recursion.
38       */
39      public static final String RECURSION_SUFFIX = ParameterFormatter.RECURSION_SUFFIX;
40  
41      /**
42       * Prefix for errors.
43       */
44      public static final String ERROR_PREFIX = ParameterFormatter.ERROR_PREFIX;
45      /**
46       * Separator for errors.
47       */
48      public static final String ERROR_SEPARATOR = ParameterFormatter.ERROR_SEPARATOR;
49      /**
50       * Separator for error messages.
51       */
52      public static final String ERROR_MSG_SEPARATOR = ParameterFormatter.ERROR_MSG_SEPARATOR;
53      /**
54       * Suffix for errors.
55       */
56      public static final String ERROR_SUFFIX = ParameterFormatter.ERROR_SUFFIX;
57  
58      private static final long serialVersionUID = -665975803997290697L;
59  
60      private static final int HASHVAL = 31;
61  
62      // storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay
63      private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
64  
65      private String messagePattern;
66      private transient Object[] argArray;
67  
68      private String formattedMessage;
69      private transient Throwable throwable;
70      private int[] indices;
71      private int usedCount;
72  
73      /**
74       * Creates a parameterized message.
75       * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
76       * where parameters should be substituted.
77       * @param arguments The arguments for substitution.
78       * @param throwable A Throwable.
79       * @deprecated Use constructor ParameterizedMessage(String, Object[], Throwable) instead
80       */
81      @Deprecated
82      public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) {
83          this.argArray = arguments;
84          this.throwable = throwable;
85          init(messagePattern);
86      }
87  
88      /**
89       * Creates a parameterized message.
90       * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
91       * where parameters should be substituted.
92       * @param arguments The arguments for substitution.
93       * @param throwable A Throwable.
94       */
95      public ParameterizedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
96          this.argArray = arguments;
97          this.throwable = throwable;
98          init(messagePattern);
99      }
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 }