blob: 9ab84a47f13dfad645cf626a873e1571301e27dd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.message;
import java.util.Arrays;
import org.apache.logging.log4j.util.Constants;
import org.apache.logging.log4j.util.StringBuilderFormattable;
import org.apache.logging.log4j.util.StringBuilders;
/**
* Handles messages that consist of a format string containing '{}' to represent each replaceable token, and
* the parameters.
* <p>
* This class was originally written for <a href="http://lilithapp.com/">Lilith</a> by Joern Huxhorn where it is
* licensed under the LGPL. It has been relicensed here with his permission providing that this attribution remain.
* </p>
*/
public class ParameterizedMessage implements Message, StringBuilderFormattable {
// Should this be configurable?
private static final int DEFAULT_STRING_BUILDER_SIZE = 255;
/**
* Prefix for recursion.
*/
public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX;
/**
* Suffix for recursion.
*/
public static final String RECURSION_SUFFIX = ParameterFormatter.RECURSION_SUFFIX;
/**
* Prefix for errors.
*/
public static final String ERROR_PREFIX = ParameterFormatter.ERROR_PREFIX;
/**
* Separator for errors.
*/
public static final String ERROR_SEPARATOR = ParameterFormatter.ERROR_SEPARATOR;
/**
* Separator for error messages.
*/
public static final String ERROR_MSG_SEPARATOR = ParameterFormatter.ERROR_MSG_SEPARATOR;
/**
* Suffix for errors.
*/
public static final String ERROR_SUFFIX = ParameterFormatter.ERROR_SUFFIX;
private static final long serialVersionUID = -665975803997290697L;
private static final int HASHVAL = 31;
// storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay
private static final ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
private String messagePattern;
private transient Object[] argArray;
private String formattedMessage;
private transient Throwable throwable;
private int[] indices;
private int usedCount;
/**
* Creates a parameterized message.
* @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
* where parameters should be substituted.
* @param arguments The arguments for substitution.
* @param throwable A Throwable.
*/
public ParameterizedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
this.argArray = arguments;
this.throwable = throwable;
init(messagePattern);
}
/**
* Constructs a ParameterizedMessage which contains the arguments converted to String as well as an optional
* Throwable.
*
* <p>If the last argument is a Throwable and is NOT used up by a placeholder in the message pattern it is returned
* in {@link #getThrowable()} and won't be contained in the created String[].
* If it is used up {@link #getThrowable()} will return null even if the last argument was a Throwable!</p>
*
* @param messagePattern the message pattern that to be checked for placeholders.
* @param arguments the argument array to be converted.
*/
public ParameterizedMessage(final String messagePattern, final Object... arguments) {
this.argArray = arguments;
init(messagePattern);
}
/**
* Constructor with a pattern and a single parameter.
* @param messagePattern The message pattern.
* @param arg The parameter.
*/
public ParameterizedMessage(final String messagePattern, final Object arg) {
this(messagePattern, new Object[]{arg});
}
/**
* Constructor with a pattern and two parameters.
* @param messagePattern The message pattern.
* @param arg0 The first parameter.
* @param arg1 The second parameter.
*/
public ParameterizedMessage(final String messagePattern, final Object arg0, final Object arg1) {
this(messagePattern, new Object[]{arg0, arg1});
}
private void init(final String messagePattern) {
this.messagePattern = messagePattern;
final int len = Math.max(1, messagePattern == null ? 0 : messagePattern.length() >> 1); // divide by 2
this.indices = new int[len]; // LOG4J2-1542 ensure non-zero array length
final int placeholders = ParameterFormatter.countArgumentPlaceholders2(messagePattern, indices);
initThrowable(argArray, placeholders);
this.usedCount = Math.min(placeholders, argArray == null ? 0 : argArray.length);
}
private void initThrowable(final Object[] params, final int usedParams) {
if (params != null) {
final int argCount = params.length;
if (usedParams < argCount && this.throwable == null && params[argCount - 1] instanceof Throwable) {
this.throwable = (Throwable) params[argCount - 1];
}
}
}
/**
* Returns the message pattern.
* @return the message pattern.
*/
@Override
public String getFormat() {
return messagePattern;
}
/**
* Returns the message parameters.
* @return the message parameters.
*/
@Override
public Object[] getParameters() {
return argArray;
}
/**
* Returns the Throwable that was given as the last argument, if any.
* It will not survive serialization. The Throwable exists as part of the message
* primarily so that it can be extracted from the end of the list of parameters
* and then be added to the LogEvent. As such, the Throwable in the event should
* not be used once the LogEvent has been constructed.
*
* @return the Throwable, if any.
*/
@Override
public Throwable getThrowable() {
return throwable;
}
/**
* Returns the formatted message.
* @return the formatted message.
*/
@Override
public String getFormattedMessage() {
if (formattedMessage == null) {
final StringBuilder buffer = getThreadLocalStringBuilder();
formatTo(buffer);
formattedMessage = buffer.toString();
StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE);
}
return formattedMessage;
}
private static StringBuilder getThreadLocalStringBuilder() {
StringBuilder buffer = threadLocalStringBuilder.get();
if (buffer == null) {
buffer = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
threadLocalStringBuilder.set(buffer);
}
buffer.setLength(0);
return buffer;
}
@Override
public void formatTo(final StringBuilder buffer) {
if (formattedMessage != null) {
buffer.append(formattedMessage);
} else {
if (indices[0] < 0) {
ParameterFormatter.formatMessage(buffer, messagePattern, argArray, usedCount);
} else {
ParameterFormatter.formatMessage2(buffer, messagePattern, argArray, usedCount, indices);
}
}
}
/**
* Replace placeholders in the given messagePattern with arguments.
*
* @param messagePattern the message pattern containing placeholders.
* @param arguments the arguments to be used to replace placeholders.
* @return the formatted message.
*/
public static String format(final String messagePattern, final Object[] arguments) {
return ParameterFormatter.format(messagePattern, arguments);
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final ParameterizedMessage that = (ParameterizedMessage) o;
if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
return false;
}
return Arrays.equals(this.argArray, that.argArray);
//if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false;
}
@Override
public int hashCode() {
int result = messagePattern != null ? messagePattern.hashCode() : 0;
result = HASHVAL * result + (argArray != null ? Arrays.hashCode(argArray) : 0);
return result;
}
/**
* Counts the number of unescaped placeholders in the given messagePattern.
*
* @param messagePattern the message pattern to be analyzed.
* @return the number of unescaped placeholders.
*/
public static int countArgumentPlaceholders(final String messagePattern) {
return ParameterFormatter.countArgumentPlaceholders(messagePattern);
}
/**
* This method performs a deep toString of the given Object.
* Primitive arrays are converted using their respective Arrays.toString methods while
* special handling is implemented for "container types", i.e. Object[], Map and Collection because those could
* contain themselves.
* <p>
* It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a
* behavior. They only check if the container is directly contained in itself, but not if a contained container
* contains the original one. Because of that, Arrays.toString(Object[]) isn't safe either.
* Confusing? Just read the last paragraph again and check the respective toString() implementation.
* </p>
* <p>
* This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o)
* would produce a relatively hard-to-debug StackOverflowError.
* </p>
* @param o The object.
* @return The String representation.
*/
public static String deepToString(final Object o) {
return ParameterFormatter.deepToString(o);
}
/**
* This method returns the same as if Object.toString() would not have been
* overridden in obj.
* <p>
* Note that this isn't 100% secure as collisions can always happen with hash codes.
* </p>
* <p>
* Copied from Object.hashCode():
* </p>
* <blockquote>
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the Java&#8482; programming language.)
* </blockquote>
*
* @param obj the Object that is to be converted into an identity string.
* @return the identity string as also defined in Object.toString()
*/
public static String identityToString(final Object obj) {
return ParameterFormatter.identityToString(obj);
}
@Override
public String toString() {
return "ParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" +
Arrays.toString(argArray) + ", throwable=" + throwable + ']';
}
}