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.StringBuilderFormattable; 022 023/** 024 * Handles messages that consist of a format string containing '{}' to represent each replaceable token, and 025 * the parameters. 026 * <p> 027 * This class was originally written for <a href="http://lilithapp.com/">Lilith</a> by Joern Huxhorn where it is 028 * licensed under the LGPL. It has been relicensed here with his permission providing that this attribution remain. 029 * </p> 030 */ 031public class ParameterizedMessage implements Message, StringBuilderFormattable { 032 /** 033 * Prefix for recursion. 034 */ 035 public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX; 036 /** 037 * Suffix for recursion. 038 */ 039 public static final String RECURSION_SUFFIX = ParameterFormatter.RECURSION_SUFFIX; 040 041 /** 042 * Prefix for errors. 043 */ 044 public static final String ERROR_PREFIX = ParameterFormatter.ERROR_PREFIX; 045 /** 046 * Separator for errors. 047 */ 048 public static final String ERROR_SEPARATOR = ParameterFormatter.ERROR_SEPARATOR; 049 /** 050 * Separator for error messages. 051 */ 052 public static final String ERROR_MSG_SEPARATOR = ParameterFormatter.ERROR_MSG_SEPARATOR; 053 /** 054 * Suffix for errors. 055 */ 056 public static final String ERROR_SUFFIX = ParameterFormatter.ERROR_SUFFIX; 057 058 private static final long serialVersionUID = -665975803997290697L; 059 060 private static final int HASHVAL = 31; 061 062 // storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay 063 private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>(); 064 065 private String messagePattern; 066 private transient Object[] argArray; 067 068 private String formattedMessage; 069 private transient Throwable throwable; 070 private int[] indices; 071 private int usedCount; 072 073 /** 074 * Creates a parameterized message. 075 * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders 076 * where parameters should be substituted. 077 * @param arguments The arguments for substitution. 078 * @param throwable A Throwable. 079 * @deprecated Use constructor ParameterizedMessage(String, Object[], Throwable) instead 080 */ 081 @Deprecated 082 public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) { 083 this.argArray = arguments; 084 this.throwable = throwable; 085 init(messagePattern); 086 } 087 088 /** 089 * Creates a parameterized message. 090 * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders 091 * where parameters should be substituted. 092 * @param arguments The arguments for substitution. 093 * @param throwable A Throwable. 094 */ 095 public ParameterizedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) { 096 this.argArray = arguments; 097 this.throwable = throwable; 098 init(messagePattern); 099 } 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™ 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}