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 */ 017package org.apache.logging.log4j.message; 018 019import java.util.Arrays; 020 021import org.apache.logging.log4j.util.Constants; 022import org.apache.logging.log4j.util.StringBuilderFormattable; 023import org.apache.logging.log4j.util.StringBuilders; 024 025/** 026 * Handles messages that consist of a format string containing '{}' to represent each replaceable token, and 027 * the parameters. 028 * <p> 029 * This class was originally written for <a href="http://lilithapp.com/">Lilith</a> by Joern Huxhorn where it is 030 * licensed under the LGPL. It has been relicensed here with his permission providing that this attribution remain. 031 * </p> 032 */ 033public class ParameterizedMessage implements Message, StringBuilderFormattable { 034 035 // Should this be configurable? 036 private static final int DEFAULT_STRING_BUILDER_SIZE = 255; 037 038 /** 039 * Prefix for recursion. 040 */ 041 public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX; 042 /** 043 * Suffix for recursion. 044 */ 045 public static final String RECURSION_SUFFIX = ParameterFormatter.RECURSION_SUFFIX; 046 047 /** 048 * Prefix for errors. 049 */ 050 public static final String ERROR_PREFIX = ParameterFormatter.ERROR_PREFIX; 051 052 /** 053 * Separator for errors. 054 */ 055 public static final String ERROR_SEPARATOR = ParameterFormatter.ERROR_SEPARATOR; 056 057 /** 058 * Separator for error messages. 059 */ 060 public static final String ERROR_MSG_SEPARATOR = ParameterFormatter.ERROR_MSG_SEPARATOR; 061 062 /** 063 * Suffix for errors. 064 */ 065 public static final String ERROR_SUFFIX = ParameterFormatter.ERROR_SUFFIX; 066 067 private static final long serialVersionUID = -665975803997290697L; 068 069 private static final int HASHVAL = 31; 070 071 // storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay 072 private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>(); 073 074 private String messagePattern; 075 private transient Object[] argArray; 076 077 private String formattedMessage; 078 private transient Throwable throwable; 079 private int[] indices; 080 private int usedCount; 081 082 /** 083 * Creates a parameterized message. 084 * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders 085 * where parameters should be substituted. 086 * @param arguments The arguments for substitution. 087 * @param throwable A Throwable. 088 * @deprecated Use constructor ParameterizedMessage(String, Object[], Throwable) instead 089 */ 090 @Deprecated 091 public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) { 092 this.argArray = arguments; 093 this.throwable = throwable; 094 init(messagePattern); 095 } 096 097 /** 098 * Creates a parameterized message. 099 * @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 if (indices[0] < 0) { 225 ParameterFormatter.formatMessage(buffer, messagePattern, argArray, usedCount); 226 } else { 227 ParameterFormatter.formatMessage2(buffer, messagePattern, argArray, usedCount, indices); 228 } 229 } 230 231 /** 232 * Replace placeholders in the given messagePattern with arguments. 233 * 234 * @param messagePattern the message pattern containing placeholders. 235 * @param arguments the arguments to be used to replace placeholders. 236 * @return the formatted message. 237 */ 238 public static String format(final String messagePattern, final Object[] arguments) { 239 return ParameterFormatter.format(messagePattern, arguments); 240 } 241 242 @Override 243 public boolean equals(final Object o) { 244 if (this == o) { 245 return true; 246 } 247 if (o == null || getClass() != o.getClass()) { 248 return false; 249 } 250 251 final ParameterizedMessage that = (ParameterizedMessage) o; 252 253 if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) { 254 return false; 255 } 256 if (!Arrays.equals(this.argArray, that.argArray)) { 257 return false; 258 } 259 //if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false; 260 261 return true; 262 } 263 264 @Override 265 public int hashCode() { 266 int result = messagePattern != null ? messagePattern.hashCode() : 0; 267 result = HASHVAL * result + (argArray != null ? Arrays.hashCode(argArray) : 0); 268 return result; 269 } 270 271 /** 272 * Counts the number of unescaped placeholders in the given messagePattern. 273 * 274 * @param messagePattern the message pattern to be analyzed. 275 * @return the number of unescaped placeholders. 276 */ 277 public static int countArgumentPlaceholders(final String messagePattern) { 278 return ParameterFormatter.countArgumentPlaceholders(messagePattern); 279 } 280 281 /** 282 * This method performs a deep toString of the given Object. 283 * Primitive arrays are converted using their respective Arrays.toString methods while 284 * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could 285 * contain themselves. 286 * <p> 287 * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a 288 * behavior. They only check if the container is directly contained in itself, but not if a contained container 289 * contains the original one. Because of that, Arrays.toString(Object[]) isn't safe either. 290 * Confusing? Just read the last paragraph again and check the respective toString() implementation. 291 * </p> 292 * <p> 293 * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o) 294 * would produce a relatively hard-to-debug StackOverflowError. 295 * </p> 296 * @param o The object. 297 * @return The String representation. 298 */ 299 public static String deepToString(final Object o) { 300 return ParameterFormatter.deepToString(o); 301 } 302 303 /** 304 * This method returns the same as if Object.toString() would not have been 305 * overridden in obj. 306 * <p> 307 * Note that this isn't 100% secure as collisions can always happen with hash codes. 308 * </p> 309 * <p> 310 * Copied from Object.hashCode(): 311 * </p> 312 * <blockquote> 313 * As much as is reasonably practical, the hashCode method defined by 314 * class {@code Object} does return distinct integers for distinct 315 * objects. (This is typically implemented by converting the internal 316 * address of the object into an integer, but this implementation 317 * technique is not required by the Java™ programming language.) 318 * </blockquote> 319 * 320 * @param obj the Object that is to be converted into an identity string. 321 * @return the identity string as also defined in Object.toString() 322 */ 323 public static String identityToString(final Object obj) { 324 return ParameterFormatter.identityToString(obj); 325 } 326 327 @Override 328 public String toString() { 329 return "ParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" + 330 Arrays.toString(argArray) + ", throwable=" + throwable + ']'; 331 } 332}