| /* |
| * 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.geode.logging.internal.log4j.api.message; |
| |
| import java.text.SimpleDateFormat; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.logging.log4j.message.Message; |
| |
| import org.apache.geode.logging.internal.log4j.api.LogWithToString; |
| |
| /** |
| * 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/> |
| * Copied into Geode from org.apache.logging.log4j.message.ParameterizedMessage |
| * (http://logging.apache.org/log4j/2.x/license.html) |
| * <p/> |
| * Geode changes include changing class name and package. Additional changes are commented with "// |
| * GEODE: note" |
| */ |
| public class GemFireParameterizedMessage implements Message { |
| |
| /** |
| * Prefix for recursion. |
| */ |
| public static final String RECURSION_PREFIX = "[..."; |
| /** |
| * Suffix for recursion. |
| */ |
| public static final String RECURSION_SUFFIX = "...]"; |
| |
| /** |
| * Prefix for errors. |
| */ |
| public static final String ERROR_PREFIX = "[!!!"; |
| /** |
| * Separator for errors. |
| */ |
| public static final String ERROR_SEPARATOR = "=>"; |
| /** |
| * Separator for error messages. |
| */ |
| public static final String ERROR_MSG_SEPARATOR = ":"; |
| /** |
| * Suffix for errors. |
| */ |
| public static final String ERROR_SUFFIX = "!!!]"; |
| |
| private static final long serialVersionUID = -665975803997290697L; |
| |
| private static final int HASHVAL = 31; |
| |
| private static final char DELIM_START = '{'; |
| private static final char DELIM_STOP = '}'; |
| private static final char ESCAPE_CHAR = '\\'; |
| |
| private final String messagePattern; |
| private final String[] stringArgs; |
| private transient Object[] argArray; |
| private transient String formattedMessage; |
| private transient Throwable throwable; |
| |
| /** |
| * Creates a parameterized message. |
| * |
| * @param messagePattern The message "format" string. This will be a String containing "{}" |
| * placeholders where parameters should be substituted. |
| * @param stringArgs The arguments for substitution. |
| * @param throwable A Throwable. |
| */ |
| public GemFireParameterizedMessage(final String messagePattern, final String[] stringArgs, |
| final Throwable throwable) { |
| this.messagePattern = messagePattern; |
| this.stringArgs = stringArgs; |
| this.throwable = throwable; |
| } |
| |
| /** |
| * Creates a parameterized message. |
| * |
| * @param messagePattern The message "format" string. This will be a String containing "{}" |
| * placeholders where parameters should be substituted. |
| * @param objectArgs The arguments for substitution. |
| * @param throwable A Throwable. |
| */ |
| public GemFireParameterizedMessage(final String messagePattern, final Object[] objectArgs, |
| final Throwable throwable) { |
| this.messagePattern = messagePattern; |
| this.throwable = throwable; |
| this.stringArgs = parseArguments(objectArgs); |
| } |
| |
| /** |
| * 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 GemFireParameterizedMessage(final String messagePattern, final Object[] arguments) { |
| this.messagePattern = messagePattern; |
| this.stringArgs = parseArguments(arguments); |
| } |
| |
| /** |
| * Constructor with a pattern and a single parameter. |
| * |
| * @param messagePattern The message pattern. |
| * @param arg The parameter. |
| */ |
| public GemFireParameterizedMessage(final String messagePattern, final Object arg) { |
| this(messagePattern, new Object[] {arg}); |
| } |
| |
| /** |
| * Constructor with a pattern and two parameters. |
| * |
| * @param messagePattern The message pattern. |
| * @param arg1 The first parameter. |
| * @param arg2 The second parameter. |
| */ |
| public GemFireParameterizedMessage(final String messagePattern, final Object arg1, |
| final Object arg2) { |
| this(messagePattern, new Object[] {arg1, arg2}); |
| } |
| |
| private String[] parseArguments(final Object[] arguments) { |
| if (arguments == null) { |
| return null; |
| } |
| final int argsCount = countArgumentPlaceholders(messagePattern); |
| int resultArgCount = arguments.length; |
| if (argsCount < arguments.length && throwable == null |
| && arguments[arguments.length - 1] instanceof Throwable) { |
| throwable = (Throwable) arguments[arguments.length - 1]; |
| resultArgCount--; |
| } |
| argArray = new Object[resultArgCount]; |
| System.arraycopy(arguments, 0, argArray, 0, resultArgCount); |
| |
| String[] strArgs; |
| if (argsCount == 1 && throwable == null && arguments.length > 1) { |
| // special case |
| strArgs = new String[1]; |
| strArgs[0] = deepToString(arguments); |
| } else { |
| strArgs = new String[resultArgCount]; |
| for (int i = 0; i < strArgs.length; i++) { |
| strArgs[i] = deepToString(arguments[i]); |
| } |
| } |
| return strArgs; |
| } |
| |
| /** |
| * Returns the formatted message. |
| * |
| * @return the formatted message. |
| */ |
| @Override |
| public String getFormattedMessage() { |
| if (formattedMessage == null) { |
| formattedMessage = formatMessage(messagePattern, stringArgs); |
| } |
| return formattedMessage; |
| } |
| |
| /** |
| * 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() { |
| if (argArray != null) { |
| return argArray; |
| } |
| return stringArgs; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| protected String formatMessage(final String msgPattern, final String[] sArgs) { |
| return format(msgPattern, sArgs); |
| } |
| |
| @Override |
| public boolean equals(final Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| final GemFireParameterizedMessage that = (GemFireParameterizedMessage) o; |
| |
| if (messagePattern != null ? !messagePattern.equals(that.messagePattern) |
| : that.messagePattern != null) { |
| return false; |
| } |
| if (!Arrays.equals(stringArgs, that.stringArgs)) { |
| 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 + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0); |
| return result; |
| } |
| |
| /** |
| * 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) { |
| if (messagePattern == null || arguments == null || arguments.length == 0) { |
| return messagePattern; |
| } |
| |
| final StringBuilder result = new StringBuilder(); |
| int escapeCounter = 0; |
| int currentArgument = 0; |
| for (int i = 0; i < messagePattern.length(); i++) { |
| final char curChar = messagePattern.charAt(i); |
| if (curChar == ESCAPE_CHAR) { |
| escapeCounter++; |
| } else { |
| if (curChar == DELIM_START && i < messagePattern.length() - 1 |
| && messagePattern.charAt(i + 1) == DELIM_STOP) { |
| // write escaped escape chars |
| final int escapedEscapes = escapeCounter / 2; |
| for (int j = 0; j < escapedEscapes; j++) { |
| result.append(ESCAPE_CHAR); |
| } |
| |
| if (escapeCounter % 2 == 1) { |
| // i.e. escaped |
| // write escaped escape chars |
| result.append(DELIM_START); |
| result.append(DELIM_STOP); |
| } else { |
| // unescaped |
| if (currentArgument < arguments.length) { |
| result.append(arguments[currentArgument]); |
| } else { |
| result.append(DELIM_START).append(DELIM_STOP); |
| } |
| currentArgument++; |
| } |
| i++; |
| escapeCounter = 0; |
| continue; |
| } |
| // any other char beside ESCAPE or DELIM_START/STOP-combo |
| // write unescaped escape chars |
| if (escapeCounter > 0) { |
| for (int j = 0; j < escapeCounter; j++) { |
| result.append(ESCAPE_CHAR); |
| } |
| escapeCounter = 0; |
| } |
| result.append(curChar); |
| } |
| } |
| return result.toString(); |
| } |
| |
| /** |
| * 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) { |
| if (messagePattern == null) { |
| return 0; |
| } |
| |
| final int delim = messagePattern.indexOf(DELIM_START); |
| |
| if (delim == -1) { |
| // special case, no placeholders at all. |
| return 0; |
| } |
| int result = 0; |
| boolean isEscaped = false; |
| for (int i = 0; i < messagePattern.length(); i++) { |
| final char curChar = messagePattern.charAt(i); |
| if (curChar == ESCAPE_CHAR) { |
| isEscaped = !isEscaped; |
| } else if (curChar == DELIM_START) { |
| if (!isEscaped && i < messagePattern.length() - 1 |
| && messagePattern.charAt(i + 1) == DELIM_STOP) { |
| result++; |
| i++; |
| } |
| isEscaped = false; |
| } else { |
| isEscaped = false; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * 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) { |
| if (o == null) { |
| return null; |
| } |
| if (o instanceof String) { |
| return (String) o; |
| } |
| final StringBuilder str = new StringBuilder(); |
| final Set<String> dejaVu = new HashSet<String>(); // that's actually a neat name ;) |
| recursiveDeepToString(o, str, dejaVu); |
| return str.toString(); |
| } |
| |
| /** |
| * 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> |
| * dejaVu is used in case of those container types to prevent an endless recursion. |
| * </p> |
| * <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 to convert into a String |
| * @param str the StringBuilder that o will be appended to |
| * @param dejaVu a list of container identities that were already used. |
| */ |
| private static void recursiveDeepToString(final Object o, final StringBuilder str, |
| final Set<String> dejaVu) { |
| if (o == null) { |
| str.append("null"); |
| return; |
| } |
| if (o instanceof String) { |
| str.append(o); |
| return; |
| } |
| |
| final Class<?> oClass = o.getClass(); |
| if (oClass.isArray()) { |
| if (oClass == byte[].class) { |
| str.append(Arrays.toString((byte[]) o)); |
| } else if (oClass == short[].class) { |
| str.append(Arrays.toString((short[]) o)); |
| } else if (oClass == int[].class) { |
| str.append(Arrays.toString((int[]) o)); |
| } else if (oClass == long[].class) { |
| str.append(Arrays.toString((long[]) o)); |
| } else if (oClass == float[].class) { |
| str.append(Arrays.toString((float[]) o)); |
| } else if (oClass == double[].class) { |
| str.append(Arrays.toString((double[]) o)); |
| } else if (oClass == boolean[].class) { |
| str.append(Arrays.toString((boolean[]) o)); |
| } else if (oClass == char[].class) { |
| str.append(Arrays.toString((char[]) o)); |
| } else { |
| // special handling of container Object[] |
| final String id = identityToString(o); |
| if (dejaVu.contains(id)) { |
| str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); |
| } else { |
| dejaVu.add(id); |
| final Object[] oArray = (Object[]) o; |
| str.append('['); |
| boolean first = true; |
| for (final Object current : oArray) { |
| if (first) { |
| first = false; |
| } else { |
| str.append(", "); |
| } |
| recursiveDeepToString(current, str, new HashSet<String>(dejaVu)); |
| } |
| str.append(']'); |
| } |
| // str.append(Arrays.deepToString((Object[]) o)); |
| } |
| } else if (o instanceof Map && !(o instanceof LogWithToString)) { |
| // GEODE: do NOT use Map handling if instanceof Geode Region |
| // special handling of container Map |
| final String id = identityToString(o); |
| if (dejaVu.contains(id)) { |
| str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); |
| } else { |
| dejaVu.add(id); |
| final Map<?, ?> oMap = (Map<?, ?>) o; |
| str.append('{'); |
| boolean isFirst = true; |
| for (final Object o1 : oMap.entrySet()) { |
| final Map.Entry<?, ?> current = (Map.Entry<?, ?>) o1; |
| if (isFirst) { |
| isFirst = false; |
| } else { |
| str.append(", "); |
| } |
| final Object key = current.getKey(); |
| final Object value = current.getValue(); |
| recursiveDeepToString(key, str, new HashSet<String>(dejaVu)); |
| str.append('='); |
| recursiveDeepToString(value, str, new HashSet<String>(dejaVu)); |
| } |
| str.append('}'); |
| } |
| } else if (o instanceof Collection && !(o instanceof LogWithToString)) { |
| // GEODE: do NOT use Collection handling if instanceof Geode EntriesSet |
| // special handling of container Collection |
| final String id = identityToString(o); |
| if (dejaVu.contains(id)) { |
| str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); |
| } else { |
| dejaVu.add(id); |
| final Collection<?> oCol = (Collection<?>) o; |
| str.append('['); |
| boolean isFirst = true; |
| for (final Object anOCol : oCol) { |
| if (isFirst) { |
| isFirst = false; |
| } else { |
| str.append(", "); |
| } |
| recursiveDeepToString(anOCol, str, new HashSet<String>(dejaVu)); |
| } |
| str.append(']'); |
| } |
| } else if (o instanceof Date) { |
| final Date date = (Date) o; |
| final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); |
| // I'll leave it like this for the moment... this could probably be optimized using |
| // ThreadLocal... |
| str.append(format.format(date)); |
| } else { |
| // it's just some other Object, we can only use toString(). |
| try { |
| str.append(o.toString()); |
| } catch (final Throwable t) { |
| str.append(ERROR_PREFIX); |
| str.append(identityToString(o)); |
| str.append(ERROR_SEPARATOR); |
| final String msg = t.getMessage(); |
| final String className = t.getClass().getName(); |
| str.append(className); |
| if (!className.equals(msg)) { |
| str.append(ERROR_MSG_SEPARATOR); |
| str.append(msg); |
| } |
| str.append(ERROR_SUFFIX); |
| } |
| } |
| } |
| |
| /** |
| * 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) { |
| if (obj == null) { |
| return null; |
| } |
| return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj)); |
| } |
| |
| @Override |
| public String toString() { |
| // GEODE: adjust toString to GemFireParameterizedMessage |
| return "GemFireParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" |
| + Arrays.toString(stringArgs) + ", throwable=" + throwable + ']'; |
| } |
| } |