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™ 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 }