/******************************************************************************* * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2019) * * contact.vitam@culture.gouv.fr * * This software is a computer program whose purpose is to implement a digital archiving back-office system managing * high volumetry securely and efficiently. * * This software is governed by the CeCILL 2.1 license under French law and abiding by the rules of distribution of free * software. You can use, modify and/ or redistribute the software under the terms of the CeCILL 2.1 license as * circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info". * * As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, * users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the * successive licensors have only limited liability. * * In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or * developing or reproducing the software by the user in light of its specific status of free software, that may mean * that it is complicated to manipulate, and that also therefore means that it is reserved for developers and * experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the * software's suitability as regards their requirements in conditions enabling the security of their systems and/or data * to be ensured and, more generally, to use and operate it in the same conditions as regards security. * * The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you * accept its terms. *******************************************************************************/ package fr.gouv.vitam.common.logging; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import fr.gouv.vitam.common.ParametersChecker; /** * Formats messages according to very simple substitution rules. Substitutions can be made 1, 2 or more arguments. * <p/> * <p/> * For example, * <p/> * * <pre> * MessageFormatter.format("Hi {}.", "there") * </pre> * <p/> * will return the string "Hi there.". * <p/> * The {} pair is called the <em>formatting anchor</em>. It serves to designate the location where arguments need to be * substituted within the message pattern. * <p/> * In case your message contains the '{' or the '}' character, you do not have to do anything special unless the '}' * character immediately follows '{'. For example, * <p/> * * <pre> * MessageFormatter.format("Set {1,2,3} is not equal to {}.", "1,2"); * </pre> * <p/> * will return the string "Set {1,2,3} is not equal to 1,2.". * <p/> * <p/> * If for whatever reason you need to place the string "{}" in the message without its <em>formatting anchor</em> * meaning, then you need to escape the '{' character with '\', that is the backslash character. Only the '{' character * should be escaped. There is no need to escape the '}' character. For example, * <p/> * * <pre> * MessageFormatter.format("Set \\{} is not equal to {}.", "1,2"); * </pre> * <p/> * will return the string "Set {} is not equal to 1,2.". * <p/> * <p/> * The escaping behavior just described can be overridden by escaping the escape character '\'. Calling * <p/> * * <pre> * MessageFormatter.format("File name is C:\\\\{}.", "file.zip"); * </pre> * <p/> * will return the string "File name is C:\file.zip". * <p/> * <p/> * The formatting conventions are different than those of {@link MessageFormat} which ships with the Java platform. This * is justified by the fact that SLF4J's implementation is 10 times faster than that of {@link MessageFormat}. This * local performance difference is both measurable and significant in the larger context of the complete logging * processing chain. * <p/> * <p/> * See also {@link #format(String, Object)}, {@link #format(String, Object, Object)} and * {@link #arrayFormat(String, Object[])} methods for more details. <br> * Inspired from Netty */ final class MessageFormatter { static final char DELIM_START = '{'; static final char DELIM_STOP = '}'; static final String DELIM_STR = "{}"; private static final char ESCAPE_CHAR = '\\'; private MessageFormatter() { // Empty } /** * Performs single argument substitution for the 'messagePattern' passed as parameter. * <p/> * For example, * <p/> * * <pre> * MessageFormatter.format("Hi {}.", "there"); * </pre> * <p/> * will return the string "Hi there.". * <p/> * * @param messagePattern The message pattern which will be parsed and formatted * @param arg The argument to be substituted in place of the formatting anchor * @return The formatted message */ static final FormattingTuple format(final String messagePattern, final Object arg) { return arrayFormat(messagePattern, new Object[] {arg}); } /** * Performs a two argument substitution for the 'messagePattern' passed as parameter. * <p/> * For example, * <p/> * * <pre> * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob"); * </pre> * <p/> * will return the string "Hi Alice. My name is Bob.". * * @param messagePattern The message pattern which will be parsed and formatted * @param argA The argument to be substituted in place of the first formatting anchor * @param argB The argument to be substituted in place of the second formatting anchor * @return The formatted message */ static final FormattingTuple format(final String messagePattern, final Object argA, final Object argB) { return arrayFormat(messagePattern, new Object[] {argA, argB}); } static final Throwable getThrowableCandidate(final Object[] argArray) { if (argArray == null || argArray.length == 0) { return null; } final Object lastEntry = argArray[argArray.length - 1]; if (lastEntry instanceof Throwable) { return (Throwable) lastEntry; } return null; } /** * Same principle as the {@link #format(String, Object)} and {@link #format(String, Object, Object)} methods except * that any number of arguments can be passed in an array. * * @param messagePattern The message pattern which will be parsed and formatted * @param argArray An array of arguments to be substituted in place of formatting anchors * @return The formatted message */ static final FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) { final Throwable throwableCandidate = getThrowableCandidate(argArray); if (messagePattern == null) { return new FormattingTuple(null, argArray, throwableCandidate); } if (argArray == null) { return new FormattingTuple(messagePattern); } int i = 0; int j; final StringBuilder sbuild = new StringBuilder(messagePattern.length() + 50); int l; for (l = 0; l < argArray.length; l++) { j = messagePattern.indexOf(DELIM_STR, i); if (j == -1) { // no more variables if (i == 0) { // this is a simple string return new FormattingTuple(messagePattern, argArray, throwableCandidate); } else { // add the tail string which contains no variables and return the result. sbuild.append(messagePattern.substring(i, messagePattern.length())); return new FormattingTuple(sbuild.toString(), argArray, throwableCandidate); } } else { if (isEscapedDelimeter(messagePattern, j)) { if (!isDoubleEscaped(messagePattern, j)) { l--; // DELIM_START was escaped, thus should not be incremented sbuild.append(messagePattern.substring(i, j - 1)).append(DELIM_START); i = j + 1; } else { // The escape character preceding the delimiter start is // itself escaped: "abc x:\\{}" // we have to consume one backward slash sbuild.append(messagePattern.substring(i, j - 1)); deeplyAppendParameter(sbuild, argArray[l], new HashMap<Object[], Void>()); i = j + 2; } } else { // normal case sbuild.append(messagePattern.substring(i, j)); deeplyAppendParameter(sbuild, argArray[l], new HashMap<Object[], Void>()); i = j + 2; } } } // append the characters following the last {} pair. sbuild.append(messagePattern.substring(i, messagePattern.length())); if (l < argArray.length - 1) { return new FormattingTuple(sbuild.toString(), argArray, throwableCandidate); } else { return new FormattingTuple(sbuild.toString(), argArray, null); } } static final boolean isEscapedDelimeter(final String messagePattern, final int delimeterStartIndex) { ParametersChecker.checkParameterNullOnly("Must not be null", messagePattern); if (delimeterStartIndex == 0) { return false; } return messagePattern.charAt(delimeterStartIndex - 1) == ESCAPE_CHAR; } static final boolean isDoubleEscaped(final String messagePattern, final int delimeterStartIndex) { ParametersChecker.checkParameterNullOnly("Must not be null", messagePattern); return delimeterStartIndex >= 2 && delimeterStartIndex - 2 > messagePattern.length() && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR; } // special treatment of array values was suggested by 'lizongbo' static final void deeplyAppendParameter(final StringBuilder sbuild, final Object o, final Map<Object[], Void> seenMap) { if (o == null) { sbuild.append("null"); return; } if (!o.getClass().isArray()) { safeObjectAppend(sbuild, o); } else { // check for primitive array types because they // unfortunately cannot be cast to Object[] if (o instanceof boolean[]) { booleanArrayAppend(sbuild, (boolean[]) o); } else if (o instanceof byte[]) { byteArrayAppend(sbuild, (byte[]) o); } else if (o instanceof char[]) { charArrayAppend(sbuild, (char[]) o); } else if (o instanceof short[]) { shortArrayAppend(sbuild, (short[]) o); } else if (o instanceof int[]) { intArrayAppend(sbuild, (int[]) o); } else if (o instanceof long[]) { longArrayAppend(sbuild, (long[]) o); } else if (o instanceof float[]) { floatArrayAppend(sbuild, (float[]) o); } else if (o instanceof double[]) { doubleArrayAppend(sbuild, (double[]) o); } else { objectArrayAppend(sbuild, (Object[]) o, seenMap); } } } private static final void safeObjectAppend(final StringBuilder sbuild, final Object o) { try { final String oAsString = o.toString(); sbuild.append(oAsString); } catch (final Exception t) { SysErrLogger.FAKE_LOGGER.ignoreLog(t); SysErrLogger.FAKE_LOGGER.syserr( "SLF4J: Failed toString() invocation on an object of type [" + o.getClass().getName() + ']' + t.getMessage()); sbuild.append("[FAILED toString()]"); } } private static final void objectArrayAppend(final StringBuilder sbuild, final Object[] a, final Map<Object[], Void> seenMap) { sbuild.append('['); if (!seenMap.containsKey(a)) { seenMap.put(a, null); final int len = a.length; for (int i = 0; i < len; i++) { deeplyAppendParameter(sbuild, a[i], seenMap); if (i != len - 1) { sbuild.append(", "); } } // allow repeats in siblings seenMap.remove(a); } else { sbuild.append("..."); } sbuild.append(']'); } private static final void booleanArrayAppend(final StringBuilder sbuild, final boolean[] a) { sbuild.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuild.append(a[i]); if (i != len - 1) { sbuild.append(", "); } } sbuild.append(']'); } private static final void byteArrayAppend(final StringBuilder sbuild, final byte[] a) { sbuild.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuild.append(a[i]); if (i != len - 1) { sbuild.append(", "); } } sbuild.append(']'); } private static final void charArrayAppend(final StringBuilder sbuild, final char[] a) { sbuild.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuild.append(a[i]); if (i != len - 1) { sbuild.append(", "); } } sbuild.append(']'); } private static final void shortArrayAppend(final StringBuilder sbuild, final short[] a) { sbuild.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuild.append(a[i]); if (i != len - 1) { sbuild.append(", "); } } sbuild.append(']'); } private static final void intArrayAppend(final StringBuilder sbuild, final int[] a) { sbuild.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuild.append(a[i]); if (i != len - 1) { sbuild.append(", "); } } sbuild.append(']'); } private static final void longArrayAppend(final StringBuilder sbuild, final long[] a) { sbuild.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuild.append(a[i]); if (i != len - 1) { sbuild.append(", "); } } sbuild.append(']'); } private static final void floatArrayAppend(final StringBuilder sbuild, final float[] a) { sbuild.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuild.append(a[i]); if (i != len - 1) { sbuild.append(", "); } } sbuild.append(']'); } private static final void doubleArrayAppend(final StringBuilder sbuild, final double[] a) { sbuild.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuild.append(a[i]); if (i != len - 1) { sbuild.append(", "); } } sbuild.append(']'); } }