/*
* 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.
This class was originally written for Lilith by Joern Huxhorn where it is
licensed under the LGPL. It has been relicensed here with his permission providing that this attribution remain.
/**
* 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.
/**
* Prefix for recursion.
*/
public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX;
Suffix for recursion.
/**
* Suffix for recursion.
*/
public static final String RECURSION_SUFFIX = ParameterFormatter.RECURSION_SUFFIX;
Prefix for errors.
/**
* Prefix for errors.
*/
public static final String ERROR_PREFIX = ParameterFormatter.ERROR_PREFIX;
Separator for errors.
/**
* Separator for errors.
*/
public static final String ERROR_SEPARATOR = ParameterFormatter.ERROR_SEPARATOR;
Separator for error messages.
/**
* Separator for error messages.
*/
public static final String ERROR_MSG_SEPARATOR = ParameterFormatter.ERROR_MSG_SEPARATOR;
Suffix for errors.
/**
* 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 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.
Params: - messagePattern – The message "format" string. This will be a String containing "{}" placeholders
where parameters should be substituted.
- arguments – The arguments for substitution.
- throwable – A Throwable.
Deprecated: Use constructor ParameterizedMessage(String, Object[], Throwable) instead
/**
* 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.
* @deprecated Use constructor ParameterizedMessage(String, Object[], Throwable) instead
*/
@Deprecated
public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) {
this.argArray = arguments;
this.throwable = throwable;
init(messagePattern);
}
Creates a parameterized message.
Params: - messagePattern – The message "format" string. This will be a String containing "{}" placeholders
where parameters should be substituted.
- arguments – The arguments for substitution.
- throwable – A Throwable.
/**
* 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.
If the last argument is a Throwable and is NOT used up by a placeholder in the message pattern it is returned in getThrowable()
and won't be contained in the created String[]. If it is used up getThrowable()
will return null even if the last argument was a Throwable!
Params: - messagePattern – the message pattern that to be checked for placeholders.
- arguments – the argument array to be converted.
/**
* 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.
Params: - messagePattern – The message pattern.
- arg – The parameter.
/**
* 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.
Params: - messagePattern – The message pattern.
- arg0 – The first parameter.
- arg1 – The second parameter.
/**
* 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.
Returns: the message pattern.
/**
* Returns the message pattern.
* @return the message pattern.
*/
@Override
public String getFormat() {
return messagePattern;
}
Returns the message parameters.
Returns: the message parameters.
/**
* 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.
Returns: the Throwable, if any.
/**
* 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.
Returns: the formatted message.
/**
* 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.
Params: - messagePattern – the message pattern containing placeholders.
- arguments – the arguments to be used to replace placeholders.
Returns: the formatted message.
/**
* 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;
}
if (!Arrays.equals(this.argArray, that.argArray)) {
return false;
}
//if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false;
return true;
}
@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.
Params: - messagePattern – the message pattern to be analyzed.
Returns: the number of unescaped placeholders.
/**
* 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.
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.
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.
Params: - o – The object.
Returns: The String representation.
/**
* 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.
Note that this isn't 100% secure as collisions can always happen with hash codes.
Copied from Object.hashCode():
As much as is reasonably practical, the hashCode method defined by class 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™ programming language.)
Params: - obj – the Object that is to be converted into an identity string.
Returns: the identity string as also defined in Object.toString()
/**
* 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™ 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 + ']';
}
}