This is an automated email from the ASF dual-hosted git repository. sk0x50 pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push: new 2ef376c IGNITE-14890 Using SLF4J log format. Fixes #175 2ef376c is described below commit 2ef376c720b8e6dc0fa43605abd74c9767a2702d Author: Vladislav Pyatkov <vldpyat...@gmail.com> AuthorDate: Fri Jun 25 01:58:53 2021 +0300 IGNITE-14890 Using SLF4J log format. Fixes #175 Signed-off-by: Slava Koptilin <slava.kopti...@gmail.com> --- .../affinity/RendezvousAffinityFunctionTest.java | 2 +- .../org/apache/ignite/internal/util/ByteUtils.java | 5 +- .../apache/ignite/internal/util/IgniteUtils.java | 1 + .../java/org/apache/ignite/lang/IgniteLogger.java | 179 +++++++-- .../apache/ignite/lang/LoggerMessageHelper.java | 443 +++++++++++++++++++++ .../tostring/IgniteToStringBuilderSelfTest.java | 4 +- .../org/apache/ignite/lang/LoggerHelperTest.java | 299 ++++++++++++++ .../network/scalecube/ITNodeRestartsTest.java | 8 +- .../internal/network/netty/ConnectionManager.java | 2 +- .../ScaleCubeDirectMarshallerTransport.java | 6 +- .../raft/client/service/RaftGroupServiceTest.java | 4 +- .../raft/server/ITJRaftCounterServerTest.java | 6 +- .../raft/server/ITSimpleCounterServerTest.java | 2 +- .../internal/raft/server/impl/RaftServerImpl.java | 4 +- .../apache/ignite/internal/app/IgnitionImpl.java | 9 +- .../marshaller/asm/AsmSerializerGenerator.java | 8 +- .../ignite/distributed/ITDistributedTableTest.java | 4 +- 17 files changed, 926 insertions(+), 60 deletions(-) diff --git a/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/RendezvousAffinityFunctionTest.java b/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/RendezvousAffinityFunctionTest.java index 1881da0..e51fd40 100644 --- a/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/RendezvousAffinityFunctionTest.java +++ b/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/RendezvousAffinityFunctionTest.java @@ -130,7 +130,7 @@ public class RendezvousAffinityFunctionTest { assertNotNull(assignment); - LOG.info("Assignment is serialized successfully [bytes=" + assignmentBytes.length + ']'); + LOG.info("Assignment is serialized successfully [bytes={}]", assignmentBytes.length); List<List<ClusterNode>> deserializedAssignment = (List<List<ClusterNode>>)ByteUtils.fromBytes(assignmentBytes); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/ByteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/ByteUtils.java index 6412df4..dd4da72 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/ByteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/ByteUtils.java @@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.apache.ignite.lang.IgniteLogger; +import org.apache.ignite.lang.LoggerMessageHelper; /** * Utility class provides various method for manipulating with bytes. @@ -118,7 +119,9 @@ public class ByteUtils { } } catch (Exception e) { - LOG.warn("Could not serialize a class [cls=" + obj.getClass().getName() + "]", e); + LOG.warn(() -> + LoggerMessageHelper.format("Could not serialize a class [cls={}]", obj.getClass().getName()), + e); return null; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 796edd1..0b61103 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -309,6 +309,7 @@ public class IgniteUtils { * * @param clsName Class name. * @param ldr Class loader. + * @param clsFilter Predicate to filter class names. * @return Class. * @throws ClassNotFoundException If class not found. */ diff --git a/modules/core/src/main/java/org/apache/ignite/lang/IgniteLogger.java b/modules/core/src/main/java/org/apache/ignite/lang/IgniteLogger.java index b99b005..a81ba46 100644 --- a/modules/core/src/main/java/org/apache/ignite/lang/IgniteLogger.java +++ b/modules/core/src/main/java/org/apache/ignite/lang/IgniteLogger.java @@ -17,15 +17,11 @@ package org.apache.ignite.lang; +import java.lang.System.Logger.Level; import java.util.Objects; +import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; -import static java.lang.System.Logger.Level.DEBUG; -import static java.lang.System.Logger.Level.ERROR; -import static java.lang.System.Logger.Level.INFO; -import static java.lang.System.Logger.Level.TRACE; -import static java.lang.System.Logger.Level.WARNING; - /** * Ignite logger wraps system logger for more convenient access. */ @@ -51,79 +47,200 @@ public class IgniteLogger { } /** - * @param msg The message. - * @param params Parameters. + * @param msg The message pattern which will be formatted and passed to the {@link System.Logger}. + * @param params A list of arguments to be substituted in place of formatting anchors. */ public void info(String msg, Object... params) { - log.log(INFO, msg, params); + logInternal(Level.INFO, msg, params); + } + + /** + * Logs a message which produces in {@code msgSupplier}, on {@link Level#INFO} level with associated exception + * {@code thrown}. + * + * @param msgSupplier A supplier function that produces a message. + * @param thrown A {@code Throwable} associated with log message; can be {@code null}. + */ + public void info(Supplier<String> msgSupplier, Throwable thrown) { + logInternalExceptional(Level.INFO, msgSupplier, thrown); } /** - * @param msg The message. - * @param params Parameters. + * @param msg The message pattern which will be passed to the {@link System.Logger}. + * @param th A {@code Throwable} associated with the log message. + */ + public void info(String msg, Throwable th) { + log.log(Level.INFO, msg, th); + } + + /** + * @param msg The message pattern which will be formatted and passed to the {@link System.Logger}. + * @param params A list of arguments to be substituted in place of formatting anchors. */ public void debug(String msg, Object... params) { - log.log(DEBUG, msg, params); + logInternal(Level.DEBUG, msg, params); + } + + /** + * Logs a message which produces in {@code msgSupplier}, on {@link Level#DEBUG} level with associated exception + * {@code thrown}. + * + * @param msgSupplier A supplier function that produces a message. + * @param thrown A {@code Throwable} associated with log message; can be {@code null}. + */ + public void debug(Supplier<String> msgSupplier, Throwable thrown) { + logInternalExceptional(Level.DEBUG, msgSupplier, thrown); } /** - * @param msg The message. + * @param msg The message pattern which will be passed to the {@link System.Logger}. * @param th A {@code Throwable} associated with the log message; */ public void debug(String msg, Throwable th) { - log.log(DEBUG, msg, th); + log.log(Level.DEBUG, msg, th); } /** - * @param msg The message. - * @param params Parameters. + * @param msg The message pattern which will be formatted and passed to the {@link System.Logger}. + * @param params A list of arguments to be substituted in place of formatting anchors. */ public void warn(String msg, Object... params) { - log.log(WARNING, msg, params); + logInternal(Level.WARNING, msg, params); + } + + /** + * Logs a message which produces in {@code msgSupplier}, on {@link Level#WARNING} level with associated exception + * {@code thrown}. + * + * @param msgSupplier A supplier function that produces a message. + * @param thrown A {@code Throwable} associated with log message; can be {@code null}. + */ + public void warn(Supplier<String> msgSupplier, Throwable thrown) { + logInternalExceptional(Level.WARNING, msgSupplier, thrown); } /** - * @param msg The message. - * @param params Parameters. + * @param msg The message pattern which will be passed to the {@link System.Logger}. + * @param th A {@code Throwable} associated with the log message. + */ + public void warn(String msg, Throwable th) { + log.log(Level.WARNING, msg, th); + } + + /** + * @param msg The message pattern which will be formatted and passed to the {@link System.Logger}. + * @param params A list of arguments to be substituted in place of formatting anchors. */ public void error(String msg, Object... params) { - log.log(ERROR, msg, params); + logInternal(Level.ERROR, msg, params); } /** - * @param msg The message. + * Logs a message which produces in {@code msgSupplier}, on {@link Level#ERROR} level with associated exception + * {@code thrown}. + * + * @param msgSupplier A supplier function that produces a message. + * @param thrown A {@code Throwable} associated with log message; can be {@code null}. + */ + public void error(Supplier<String> msgSupplier, Throwable thrown) { + logInternalExceptional(Level.ERROR, msgSupplier, thrown); + } + + /** + * @param msg The message pattern which will be passed to the {@link System.Logger}. * @param th A {@code Throwable} associated with the log message. */ public void error(String msg, Throwable th) { - log.log(ERROR, msg, th); + log.log(Level.ERROR, msg, th); } /** - * @param msg The message. - * @param params Parameters. + * @param msg The message pattern which will be formatted and passed to the {@link System.Logger}. + * @param params A list of arguments to be substituted in place of formatting anchors. */ public void trace(String msg, Object... params) { - log.log(TRACE, msg, params); + logInternal(Level.TRACE, msg, params); + } + + /** + * Logs a message which produces in {@code msgSupplier}, on {@link Level#TRACE} level with associated exception + * {@code thrown}. + * + * @param msgSupplier A supplier function that produces a message. + * @param thrown A {@code Throwable} associated with log message; can be {@code null}. + */ + public void trace(Supplier<String> msgSupplier, Throwable thrown) { + logInternalExceptional(Level.TRACE, msgSupplier, thrown); } /** - * @return {@code true} if the TRACE log message level is currently being logged. + * @param msg The message pattern which will be passed to the {@link System.Logger}. + * @param th A {@code Throwable} associated with the log message. + */ + public void trace(String msg, Throwable th) { + log.log(Level.TRACE, msg, th); + } + + /** + * Logs a message with an optional list of parameters. + * + * @param level One of the log message level identifiers. + * @param msg The string message format in {@link LoggerMessageHelper} format. + * @param params An optional list of parameters to the message (may be none). + * @throws NullPointerException If {@code level} is {@code null}. + */ + private void logInternal(Level level, String msg, Object... params) { + Objects.requireNonNull(level); + + if (!log.isLoggable(level)) + return; + + log.log(level, LoggerMessageHelper.arrayFormat(msg, params)); + } + + /** + * Logs a lazily supplied message associated with a given throwable. + * + * @param level One of the log message level identifiers. + * @param msgSupplier A supplier function that produces a message. + * @param thrown A {@code Throwable} associated with log message; can be {@code null}. + * @throws NullPointerException If {@code level} is {@code null}, or {@code msgSupplier} is {@code null}. + */ + private void logInternalExceptional(Level level, Supplier<String> msgSupplier, Throwable thrown) { + Objects.requireNonNull(level); + Objects.requireNonNull(msgSupplier); + + if (!log.isLoggable(level)) + return; + + log.log(level, msgSupplier.get(), thrown); + } + + /** + * @return {@code true} if the {@link Level#TRACE} log message level is currently being logged. */ public boolean isTraceEnabled() { - return log.isLoggable(TRACE); + return log.isLoggable(Level.TRACE); } /** - * @return {@code true} if the DEBUG log message level is currently being logged. + * @return {@code true} if the {@link Level#DEBUG} log message level is currently being logged. */ public boolean isDebugEnabled() { - return log.isLoggable(DEBUG); + return log.isLoggable(Level.DEBUG); } /** - * @return {@code true} if the INFO log message level is currently being logged. + * @return {@code true} if the {@link Level#INFO} log message level is currently being logged. */ public boolean isInfoEnabled() { - return log.isLoggable(INFO); + return log.isLoggable(Level.INFO); + } + + /** + * @return {@code true} if the {@link Level#WARNING} log message level is currently being logged. + */ + public boolean isWarnEnabled() { + return log.isLoggable(Level.WARNING); } } diff --git a/modules/core/src/main/java/org/apache/ignite/lang/LoggerMessageHelper.java b/modules/core/src/main/java/org/apache/ignite/lang/LoggerMessageHelper.java new file mode 100644 index 0000000..6ae8116 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/lang/LoggerMessageHelper.java @@ -0,0 +1,443 @@ +/* + * 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.ignite.lang; + +import java.util.HashSet; + +/** + * Formats messages according to very simple substitution rules. Substitutions can be made 1, 2 or more arguments. + * + * <p> + * For example, + * + * <pre> + * LoggerMessageHelper.format("Hi {}.", "there").get1(); + * </pre> + * + * 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, + * + * <pre> + * LoggerMessageHelper.format("Set {1,2,3} is not equal to {}.", "1,2").get1(); + * </pre> + * + * will return the string "Set {1,2,3} is not equal to 1,2.". + * + * <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, + * + * <pre> + * LoggerMessageHelper.format("Set \\{} is not equal to {}.", "1,2").get1(); + * </pre> + * + * will return the string "Set {} is not equal to 1,2.". + * + * <p> + * The escaping behavior just described can be overridden by escaping the escape character '\'. Calling + * + * <pre> + * LoggerMessageHelper.format("File name is C:\\\\{}.", "file.zip").get1(); + * </pre> + * + * will return the string "File name is C:\file.zip". + */ +public final class LoggerMessageHelper { + /** Left brace. */ + private static final char DELIM_START = '{'; + + /** Formatting anchor. */ + private static final String DELIM_STR = "{}"; + + /** Excape character. */ + private static final char ESCAPE_CHAR = '\\'; + + /** + * Replaces all substitutions in the messagePattern. + * + * @param messagePattern Message with formatting anchor. + * @param params Parameters. + * @return A formatted message. + */ + public static String format(final String messagePattern, final Object... params) { + return arrayFormat(messagePattern, params); + } + + /** + * Replaces all substitutions in the messagePattern. Assumes that params only contains arguments with no throwable + * as last element. + * + * @param messagePattern Message with formatting anchor. + * @param params Parameters. + */ + static String arrayFormat(final String messagePattern, final Object[] params) { + if (messagePattern == null) + return null; + + if (params == null) + return messagePattern; + + int i = 0; + int j; + // use string builder for better multicore performance + StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); + + int L; + + for (L = 0; L < params.length; L++) { + + j = messagePattern.indexOf(DELIM_STR, i); + + if (j == -1) { + // no more variables + if (i == 0) { // this is a simple string + return messagePattern; + } + else { // add the tail string which contains no variables and return + // the result. + sbuf.append(messagePattern, i, messagePattern.length()); + + return sbuf.toString(); + } + } + else { + if (isEscapedDelimeter(messagePattern, j)) { + if (!isDoubleEscaped(messagePattern, j)) { + L--; // DELIM_START was escaped, thus should not be incremented + + sbuf.append(messagePattern, i, j - 1); + + sbuf.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 + sbuf.append(messagePattern, i, j - 1); + + deeplyAppendParameter(sbuf, params[L], new HashSet<>()); + + i = j + 2; + } + } + else { + // normal case + sbuf.append(messagePattern, i, j); + + deeplyAppendParameter(sbuf, params[L], new HashSet<>()); + + i = j + 2; + } + } + } + // append the characters following the last {} pair. + sbuf.append(messagePattern, i, messagePattern.length()); + + return sbuf.toString(); + } + + /** + * Checks messagePattern has a delimiter at delimiterStartIndex position. + * + * @param messagePattern Message pattern. + * @param delimiterStartIndex Checked position. + * @return True if the char is delimiter, false otherwise. + */ + private static boolean isEscapedDelimeter(String messagePattern, int delimiterStartIndex) { + if (delimiterStartIndex == 0) + return false; + + char potentialEscape = messagePattern.charAt(delimiterStartIndex - 1); + + return potentialEscape == ESCAPE_CHAR; + } + + /** + * Checks messagePattern has a double delimiter at delimiterStartIndex position. + * + * @param messagePattern Message pattern. + * @param delimiterStartIndex Checked position. + * @return True if a double delimiter is in this position, otherwise false. + */ + private static boolean isDoubleEscaped(String messagePattern, int delimiterStartIndex) { + return delimiterStartIndex >= 2 && messagePattern.charAt(delimiterStartIndex - 2) == ESCAPE_CHAR; + } + + /** + * @param sbuf Builder that contains a string for append. + * @param o Object to append. + * @param seenSet Set of the objects that already appended. + */ + private static void deeplyAppendParameter(StringBuilder sbuf, Object o, HashSet<Object[]> seenSet) { + if (o == null) { + sbuf.append("null"); + + return; + } + if (!o.getClass().isArray()) + safeObjectAppend(sbuf, o); + else { + // check for primitive array types because they + // unfortunately cannot be cast to Object[] + if (o instanceof boolean[]) + booleanArrayAppend(sbuf, (boolean[])o); + else if (o instanceof byte[]) + byteArrayAppend(sbuf, (byte[])o); + else if (o instanceof char[]) + charArrayAppend(sbuf, (char[])o); + else if (o instanceof short[]) + shortArrayAppend(sbuf, (short[])o); + else if (o instanceof int[]) + intArrayAppend(sbuf, (int[])o); + else if (o instanceof long[]) + longArrayAppend(sbuf, (long[])o); + else if (o instanceof float[]) + floatArrayAppend(sbuf, (float[])o); + else if (o instanceof double[]) + doubleArrayAppend(sbuf, (double[])o); + else + objectArrayAppend(sbuf, (Object[])o, seenSet); + } + } + + /** + * Appends a string representation for an object, even if the object throws exception on a {@link Object#toString()} + * invocation. + * + * @param sbuf Builder that contains a string for append. + * @param o An object that will be appended. + */ + private static void safeObjectAppend(StringBuilder sbuf, Object o) { + try { + String oAsString = o.toString(); + + sbuf.append(oAsString); + } + catch (Throwable t) { + sbuf.append("Failed toString() invocation on an object of type [cls=" + o.getClass().getName() + + ", errMsg=" + t.getClass().getName() + + ", errMsg=" + t.getMessage() + ']'); + } + } + + /** + * Appends a object array to string. + * + * @param sbuf The builder contains a string before. + * @param a Object array. + * @param seenSet Set of the objects that already appended. + */ + private static void objectArrayAppend(StringBuilder sbuf, Object[] a, HashSet<Object[]> seenSet) { + sbuf.append('['); + + if (!seenSet.contains(a)) { + seenSet.add(a); + + final int len = a.length; + + for (int i = 0; i < len; i++) { + deeplyAppendParameter(sbuf, a[i], seenSet); + + if (i != len - 1) + sbuf.append(", "); + } + // allow repeats in siblings + seenSet.remove(a); + } + else + sbuf.append("..."); + + sbuf.append(']'); + } + + /** + * Appends an string representation of boolean array. + * + * @param sbuf The builder contains a string before. + * @param a Boolean array. + */ + private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) { + sbuf.append('['); + + final int len = a.length; + + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + + if (i != len - 1) + sbuf.append(", "); + } + + sbuf.append(']'); + } + + /** + * Appends an string representation of byte array. + * + * @param sbuf The builder contains a string before. + * @param a Byte array. + */ + private static void byteArrayAppend(StringBuilder sbuf, byte[] a) { + sbuf.append('['); + + final int len = a.length; + + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + + if (i != len - 1) + sbuf.append(", "); + } + + sbuf.append(']'); + } + + /** + * Appends an string representation of char array. + * + * @param sbuf The builder contains a string before. + * @param a Char array. + */ + private static void charArrayAppend(StringBuilder sbuf, char[] a) { + sbuf.append('['); + + final int len = a.length; + + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + + if (i != len - 1) + sbuf.append(", "); + } + + sbuf.append(']'); + } + + /** + * Appends an string representation of short array. + * + * @param sbuf The builder contains a string before. + * @param a Short array. + */ + private static void shortArrayAppend(StringBuilder sbuf, short[] a) { + sbuf.append('['); + + final int len = a.length; + + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + + if (i != len - 1) + sbuf.append(", "); + } + + sbuf.append(']'); + } + + /** + * Appends an string representation of integer array. + * + * @param sbuf The builder contains a string before. + * @param a Integer array. + */ + private static void intArrayAppend(StringBuilder sbuf, int[] a) { + sbuf.append('['); + + final int len = a.length; + + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + + if (i != len - 1) + sbuf.append(", "); + } + + sbuf.append(']'); + } + + /** + * Appends a string representation of long array. + * + * @param sbuf The builder contains a string before. + * @param a Long array. + */ + private static void longArrayAppend(StringBuilder sbuf, long[] a) { + sbuf.append('['); + + final int len = a.length; + + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + + if (i != len - 1) + sbuf.append(", "); + } + + sbuf.append(']'); + } + + /** + * Appends a string representation of float array. + * + * @param sbuf The builder contains a string before. + * @param a Float array. + */ + private static void floatArrayAppend(StringBuilder sbuf, float[] a) { + sbuf.append('['); + + final int len = a.length; + + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + + if (i != len - 1) + sbuf.append(", "); + } + + sbuf.append(']'); + } + + /** + * Appends a string representation of double array. + * + * @param sbuf The builder contains a string before. + * @param a Double array. + */ + private static void doubleArrayAppend(StringBuilder sbuf, double[] a) { + sbuf.append('['); + + final int len = a.length; + + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + + if (i != len - 1) + sbuf.append(", "); + } + + sbuf.append(']'); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/tostring/IgniteToStringBuilderSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/tostring/IgniteToStringBuilderSelfTest.java index 939edb7..68114a9 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/tostring/IgniteToStringBuilderSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/tostring/IgniteToStringBuilderSelfTest.java @@ -225,14 +225,14 @@ public class IgniteToStringBuilderSelfTest extends IgniteAbstractTest { for (int i = 0; i < 100000; i++) obj.toStringManual(); - logger().info("Manual toString() took: " + (System.currentTimeMillis() - start) + "ms"); + logger().info("Manual toString() took: {}ms", System.currentTimeMillis() - start); start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) obj.toStringAutomatic(); - logger().info("Automatic toString() took: " + (System.currentTimeMillis() - start) + "ms"); + logger().info("Automatic toString() took: {}ms", System.currentTimeMillis() - start); } /** diff --git a/modules/core/src/test/java/org/apache/ignite/lang/LoggerHelperTest.java b/modules/core/src/test/java/org/apache/ignite/lang/LoggerHelperTest.java new file mode 100644 index 0000000..5b9dc91 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/lang/LoggerHelperTest.java @@ -0,0 +1,299 @@ +/* + * 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.ignite.lang; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test for logger helper. + */ +public class LoggerHelperTest { + /** Message parameter 1. */ + Integer i1 = new Integer(1); + + /** Message parameter 2. */ + Integer i2 = new Integer(2); + + /** Message parameter 3. */ + Integer i3 = new Integer(3); + + /** Array message parameter 0. */ + Integer[] ia0 = new Integer[] {i1, i2, i3}; + + /** Array message parameter 1. */ + Integer[] ia1 = new Integer[] {new Integer(10), new Integer(20), new Integer(30)}; + + /** Variable for the result message. */ + String result; + + /** + * Tests {@code null} message pattern. + */ + @Test + public void testNull() { + result = LoggerMessageHelper.format(null, i1); + assertEquals(null, result); + } + + /** + * Tests parameters are {@code null}'s. + */ + @Test + public void nullParametersShouldBeHandledWithoutBarfing() { + result = LoggerMessageHelper.format("Value is {}.", new Object[] {null}); + assertEquals("Value is null.", result); + + result = LoggerMessageHelper.format("Val1 is {}, val2 is {}.", null, null); + assertEquals("Val1 is null, val2 is null.", result); + + result = LoggerMessageHelper.format("Val1 is {}, val2 is {}.", i1, null); + assertEquals("Val1 is 1, val2 is null.", result); + + result = LoggerMessageHelper.format("Val1 is {}, val2 is {}.", null, i2); + assertEquals("Val1 is null, val2 is 2.", result); + + result = LoggerMessageHelper.format("Val1 is {}, val2 is {}, val3 is {}", new Integer[] {null, null, null}); + assertEquals("Val1 is null, val2 is null, val3 is null", result); + + result = LoggerMessageHelper.format("Val1 is {}, val2 is {}, val3 is {}", new Integer[] {null, i2, i3}); + assertEquals("Val1 is null, val2 is 2, val3 is 3", result); + + result = LoggerMessageHelper.format("Val1 is {}, val2 is {}, val3 is {}", new Integer[] {null, null, i3}); + assertEquals("Val1 is null, val2 is null, val3 is 3", result); + } + + /** + * Tests the result string when parameter is only one. + */ + @Test + public void verifyOneParameterIsHandledCorrectly() { + result = LoggerMessageHelper.format("Value is {}.", i3); + assertEquals("Value is 3.", result); + + result = LoggerMessageHelper.format("Value is {", i3); + assertEquals("Value is {", result); + + result = LoggerMessageHelper.format("{} is larger than 2.", i3); + assertEquals("3 is larger than 2.", result); + + result = LoggerMessageHelper.format("No subst", i3); + assertEquals("No subst", result); + + result = LoggerMessageHelper.format("Incorrect {subst", i3); + assertEquals("Incorrect {subst", result); + + result = LoggerMessageHelper.format("Value is {bla} {}", i3); + assertEquals("Value is {bla} 3", result); + + result = LoggerMessageHelper.format("Escaped \\{} subst", i3); + assertEquals("Escaped {} subst", result); + + result = LoggerMessageHelper.format("{Escaped", i3); + assertEquals("{Escaped", result); + + result = LoggerMessageHelper.format("\\{}Escaped", i3); + assertEquals("{}Escaped", result); + + result = LoggerMessageHelper.format("File name is {{}}.", "App folder.zip"); + assertEquals("File name is {App folder.zip}.", result); + + // escaping the escape character + result = LoggerMessageHelper.format("File name is C:\\\\{}.", "App folder.zip"); + assertEquals("File name is C:\\App folder.zip.", result); + } + + /** + * Tests the result string when two parameters. + */ + @Test + public void testTwoParameters() { + result = LoggerMessageHelper.format("Value {} is smaller than {}.", i1, i2); + assertEquals("Value 1 is smaller than 2.", result); + + result = LoggerMessageHelper.format("Value {} is smaller than {}", i1, i2); + assertEquals("Value 1 is smaller than 2", result); + + result = LoggerMessageHelper.format("{}{}", i1, i2); + assertEquals("12", result); + + result = LoggerMessageHelper.format("Val1={}, Val2={", i1, i2); + assertEquals("Val1=1, Val2={", result); + + result = LoggerMessageHelper.format("Value {} is smaller than \\{}", i1, i2); + assertEquals("Value 1 is smaller than {}", result); + + result = LoggerMessageHelper.format("Value {} is smaller than \\{} tail", i1, i2); + assertEquals("Value 1 is smaller than {} tail", result); + + result = LoggerMessageHelper.format("Value {} is smaller than \\{", i1, i2); + assertEquals("Value 1 is smaller than \\{", result); + + result = LoggerMessageHelper.format("Value {} is smaller than {tail", i1, i2); + assertEquals("Value 1 is smaller than {tail", result); + + result = LoggerMessageHelper.format("Value \\{} is smaller than {}", i1, i2); + assertEquals("Value {} is smaller than 1", result); + } + + /** + * + */ + @Test + public void testExceptionIn_toString() { + Object o = new Object() { + public String toString() { + throw new IllegalStateException("a"); + } + }; + result = LoggerMessageHelper.format("Troublesome object {}", o); + assertEquals("Troublesome object Failed toString() invocation on an object of type [cls=" + o.getClass().getName() + + ", errMsg=java.lang.IllegalStateException, errMsg=a]", result); + } + + /** + * + */ + @Test + public void testNullArray() { + String msg0 = "msg0"; + String msg1 = "msg1 {}"; + String msg2 = "msg2 {} {}"; + String msg3 = "msg3 {} {} {}"; + + Object[] args = null; + + result = LoggerMessageHelper.format(msg0, args); + assertEquals(msg0, result); + + result = LoggerMessageHelper.format(msg1, args); + assertEquals(msg1, result); + + result = LoggerMessageHelper.format(msg2, args); + assertEquals(msg2, result); + + result = LoggerMessageHelper.format(msg3, args); + assertEquals(msg3, result); + } + + /** + * Tests the case when the parameters are supplied in a single array + */ + @Test + public void testArrayFormat() { + result = LoggerMessageHelper.format("Value {} is smaller than {} and {}.", ia0); + assertEquals("Value 1 is smaller than 2 and 3.", result); + + result = LoggerMessageHelper.format("{}{}{}", ia0); + assertEquals("123", result); + + result = LoggerMessageHelper.format("Value {} is smaller than {}.", ia0); + assertEquals("Value 1 is smaller than 2.", result); + + result = LoggerMessageHelper.format("Value {} is smaller than {}", ia0); + assertEquals("Value 1 is smaller than 2", result); + + result = LoggerMessageHelper.format("Val={}, {, Val={}", ia0); + assertEquals("Val=1, {, Val=2", result); + + result = LoggerMessageHelper.format("Val={}, {, Val={}", ia0); + assertEquals("Val=1, {, Val=2", result); + + result = LoggerMessageHelper.format("Val1={}, Val2={", ia0); + assertEquals("Val1=1, Val2={", result); + } + + /** + * + */ + @Test + public void testArrayValues() { + Integer p0 = i1; + Integer[] p1 = new Integer[] {i2, i3}; + + result = LoggerMessageHelper.format("{}{}", p0, p1); + assertEquals("1[2, 3]", result); + + // Integer[] + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", p1}); + assertEquals("a[2, 3]", result); + + // byte[] + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", new byte[] {1, 2}}); + assertEquals("a[1, 2]", result); + + // int[] + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", new int[] {1, 2}}); + assertEquals("a[1, 2]", result); + + // float[] + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", new float[] {1, 2}}); + assertEquals("a[1.0, 2.0]", result); + + // double[] + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", new double[] {1, 2}}); + assertEquals("a[1.0, 2.0]", result); + } + + /** + * + */ + @Test + public void testMultiDimensionalArrayValues() { + Integer[][] multiIntegerA = new Integer[][] {ia0, ia1}; + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", multiIntegerA}); + assertEquals("a[[1, 2, 3], [10, 20, 30]]", result); + + int[][] multiIntA = new int[][] {{1, 2}, {10, 20}}; + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", multiIntA}); + assertEquals("a[[1, 2], [10, 20]]", result); + + float[][] multiFloatA = new float[][] {{1, 2}, {10, 20}}; + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", multiFloatA}); + assertEquals("a[[1.0, 2.0], [10.0, 20.0]]", result); + + Object[][] multiOA = new Object[][] {ia0, ia1}; + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", multiOA}); + assertEquals("a[[1, 2, 3], [10, 20, 30]]", result); + + Object[][][] _3DOA = new Object[][][] {multiOA, multiOA}; + result = LoggerMessageHelper.format("{}{}", new Object[] {"a", _3DOA}); + assertEquals("a[[[1, 2, 3], [10, 20, 30]], [[1, 2, 3], [10, 20, 30]]]", result); + } + + /** + * + */ + @Test + public void testCyclicArrays() { + { + Object[] cyclicA = new Object[1]; + cyclicA[0] = cyclicA; + assertEquals("[[...]]", LoggerMessageHelper.format("{}", cyclicA)); + } + { + Object[] a = new Object[2]; + a[0] = i1; + Object[] c = new Object[] {i3, a}; + Object[] b = new Object[] {i2, c}; + a[1] = b; + assertEquals("1[2, [3, [1, [...]]]]", LoggerMessageHelper.format("{}{}", a)); + } + } +} diff --git a/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITNodeRestartsTest.java b/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITNodeRestartsTest.java index 7eb528c..7426fe7 100644 --- a/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITNodeRestartsTest.java +++ b/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITNodeRestartsTest.java @@ -80,17 +80,17 @@ class ITNodeRestartsTest { int idx0 = 0; int idx1 = 2; - LOG.info("Shutdown " + addresses.get(idx0)); + LOG.info("Shutdown {}", addresses.get(idx0)); services.get(idx0).shutdown(); - LOG.info("Shutdown " + addresses.get(idx1)); + LOG.info("Shutdown {}", addresses.get(idx1)); services.get(idx1).shutdown(); - LOG.info("Starting " + addresses.get(idx0)); + LOG.info("Starting {}", addresses.get(idx0)); ClusterService svc0 = startNetwork(addresses.get(idx0), initPort + idx0, addresses); services.set(idx0, svc0); - LOG.info("Starting " + addresses.get(idx1)); + LOG.info("Starting {}", addresses.get(idx1)); ClusterService svc2 = startNetwork(addresses.get(idx1), initPort + idx1, addresses); services.set(idx1, svc2); diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/netty/ConnectionManager.java b/modules/network/src/main/java/org/apache/ignite/internal/network/netty/ConnectionManager.java index 7c9cf72..7930e2d 100644 --- a/modules/network/src/main/java/org/apache/ignite/internal/network/netty/ConnectionManager.java +++ b/modules/network/src/main/java/org/apache/ignite/internal/network/netty/ConnectionManager.java @@ -243,7 +243,7 @@ public class ConnectionManager { clientWorkerGroup.shutdownGracefully(0L, 15, TimeUnit.SECONDS).sync(); } catch (Exception e) { - LOG.warn("Failed to stop the ConnectionManager: " + e.getMessage()); + LOG.warn("Failed to stop the ConnectionManager: {}", e.getMessage()); } } diff --git a/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java b/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java index fd50595..5a3e61d 100644 --- a/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java +++ b/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java @@ -97,7 +97,7 @@ class ScaleCubeDirectMarshallerTransport implements Transport { .doFinally(s -> onStop.onComplete()) .subscribe( null, - ex -> LOG.warn("Failed to stop {0}: {1}", address, ex.toString()) + ex -> LOG.warn("Failed to stop {}: {}", address, ex.toString()) ); } @@ -127,12 +127,12 @@ class ScaleCubeDirectMarshallerTransport implements Transport { */ private Mono<Void> doStop() { return Mono.defer(() -> { - LOG.info("Stopping {0}", address); + LOG.info("Stopping {}", address); // Complete incoming messages observable sink.complete(); - LOG.info("Stopped {0}", address); + LOG.info("Stopped {}", address); return Mono.empty(); }); } diff --git a/modules/raft-client/src/test/java/org/apache/ignite/raft/client/service/RaftGroupServiceTest.java b/modules/raft-client/src/test/java/org/apache/ignite/raft/client/service/RaftGroupServiceTest.java index c31687e..ca0c346 100644 --- a/modules/raft-client/src/test/java/org/apache/ignite/raft/client/service/RaftGroupServiceTest.java +++ b/modules/raft-client/src/test/java/org/apache/ignite/raft/client/service/RaftGroupServiceTest.java @@ -105,7 +105,7 @@ public class RaftGroupServiceTest { void before(TestInfo testInfo) { when(cluster.messagingService()).thenReturn(messagingService); - LOG.info(">>>> Starting test " + testInfo.getTestMethod().orElseThrow().getName()); + LOG.info(">>>> Starting test {}", testInfo.getTestMethod().orElseThrow().getName()); } /** @@ -363,7 +363,7 @@ public class RaftGroupServiceTest { timer.schedule(new TimerTask() { @Override public void run() { - LOG.info("Set leader {0}", NODES.get(1)); + LOG.info("Set leader {}", NODES.get(1)); RaftGroupServiceTest.this.leader = NODES.get(1); } diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ITJRaftCounterServerTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ITJRaftCounterServerTest.java index 287a1d7..f3c19cc 100644 --- a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ITJRaftCounterServerTest.java +++ b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ITJRaftCounterServerTest.java @@ -120,7 +120,7 @@ class ITJRaftCounterServerTest extends RaftServerAbstractTest { /** */ @BeforeEach void before(TestInfo testInfo) { - LOG.info(">>>>>>>>>>>>>>> Start test method: " + testInfo.getTestMethod().orElseThrow().getName()); + LOG.info(">>>>>>>>>>>>>>> Start test method: {}", testInfo.getTestMethod().orElseThrow().getName()); dataPath = TestUtils.mkTempDir(); } @@ -140,7 +140,7 @@ class ITJRaftCounterServerTest extends RaftServerAbstractTest { assertTrue(Utils.delete(new File(dataPath)), "Failed to delete " + dataPath); - LOG.info(">>>>>>>>>>>>>>> End test method: " + testInfo.getTestMethod().orElseThrow().getName()); + LOG.info(">>>>>>>>>>>>>>> End test method: {}", testInfo.getTestMethod().orElseThrow().getName()); } /** @@ -555,7 +555,7 @@ class ITJRaftCounterServerTest extends RaftServerAbstractTest { for (int i = start; i <= stop; i++) { val = client.<Long>run(new IncrementAndGetCommand(i)).get(); - LOG.info("Val=" + val + ", i=" + i); + LOG.info("Val={}, i={}", val, i); } return val; diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ITSimpleCounterServerTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ITSimpleCounterServerTest.java index efb701c..eb2fc18 100644 --- a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ITSimpleCounterServerTest.java +++ b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ITSimpleCounterServerTest.java @@ -70,7 +70,7 @@ class ITSimpleCounterServerTest extends RaftServerAbstractTest { */ @BeforeEach void before(TestInfo testInfo) { - LOG.info(">>>> Starting test " + testInfo.getTestMethod().orElseThrow().getName()); + LOG.info(">>>> Starting test {}", testInfo.getTestMethod().orElseThrow().getName()); String id = "localhost:" + PORT; diff --git a/modules/raft/src/main/java/org/apache/ignite/internal/raft/server/impl/RaftServerImpl.java b/modules/raft/src/main/java/org/apache/ignite/internal/raft/server/impl/RaftServerImpl.java index 08bcb3e..8aea56e 100644 --- a/modules/raft/src/main/java/org/apache/ignite/internal/raft/server/impl/RaftServerImpl.java +++ b/modules/raft/src/main/java/org/apache/ignite/internal/raft/server/impl/RaftServerImpl.java @@ -122,7 +122,7 @@ public class RaftServerImpl implements RaftServer { writeWorker.setDaemon(true); writeWorker.start(); - LOG.info("Started replication server [node=" + service + ']'); + LOG.info("Started replication server [node={}]", service); } /** {@inheritDoc} */ @@ -159,7 +159,7 @@ public class RaftServerImpl implements RaftServer { writeWorker.interrupt(); writeWorker.join(); - LOG.info("Stopped replication server [node=" + service.toString() + ']'); + LOG.info("Stopped replication server [node={}]", service); } /** diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java index b564362..1e255e6 100644 --- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java +++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java @@ -17,11 +17,11 @@ package org.apache.ignite.internal.app; +import io.netty.util.internal.StringUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import io.netty.util.internal.StringUtil; import org.apache.ignite.app.Ignite; import org.apache.ignite.app.Ignition; import org.apache.ignite.configuration.RootKey; @@ -44,6 +44,7 @@ import org.apache.ignite.internal.table.distributed.TableManager; import org.apache.ignite.internal.vault.VaultManager; import org.apache.ignite.internal.vault.impl.VaultServiceImpl; import org.apache.ignite.lang.IgniteLogger; +import org.apache.ignite.lang.LoggerMessageHelper; import org.apache.ignite.network.ClusterLocalConfiguration; import org.apache.ignite.network.ClusterService; import org.apache.ignite.network.MessageSerializationRegistryImpl; @@ -111,7 +112,7 @@ public class IgnitionImpl implements Ignition { locConfigurationMgr.bootstrap(jsonStrBootstrapCfg, ConfigurationType.LOCAL); } catch (Exception e) { - LOG.warn("Unable to parse user specific configuration, default configuration will be used: " + e.getMessage()); + LOG.warn("Unable to parse user specific configuration, default configuration will be used: {}", e.getMessage()); } else if (jsonStrBootstrapCfg != null) LOG.warn("User specific configuration will be ignored, cause vault was bootstrapped with pds configuration"); @@ -191,6 +192,8 @@ public class IgnitionImpl implements Ignition { String banner = String.join("\n", BANNER); - LOG.info(banner + '\n' + " ".repeat(22) + "Apache Ignite ver. " + ver + '\n'); + LOG.info(() -> + LoggerMessageHelper.format("{}\n" + " ".repeat(22) + "Apache Ignite ver. {}\n", banner, ver), + null); } } diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/marshaller/asm/AsmSerializerGenerator.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/marshaller/asm/AsmSerializerGenerator.java index 14712b3..5cf6bb5 100644 --- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/marshaller/asm/AsmSerializerGenerator.java +++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/marshaller/asm/AsmSerializerGenerator.java @@ -102,12 +102,12 @@ public class AsmSerializerGenerator implements SerializerFactory { compilationTime = System.nanoTime() - compilationTime; if (LOG.isTraceEnabled()) { - LOG.trace("ASM serializer created: codeGenStage=" + TimeUnit.NANOSECONDS.toMicros(generation) + "us" + - ", compileStage=" + TimeUnit.NANOSECONDS.toMicros(compilationTime) + "us." + "Code: " + writer.toString()); + LOG.trace("ASM serializer created: codeGenStage={}us, compileStage={}us. Code: {}", + TimeUnit.NANOSECONDS.toMicros(generation), TimeUnit.NANOSECONDS.toMicros(compilationTime), writer); } else if (LOG.isDebugEnabled()) { - LOG.debug("ASM serializer created: codeGenStage=" + TimeUnit.NANOSECONDS.toMicros(generation) + "us" + - ", compileStage=" + TimeUnit.NANOSECONDS.toMicros(compilationTime) + "us."); + LOG.debug("ASM serializer created: codeGenStage={}us, compileStage={}us.", + TimeUnit.NANOSECONDS.toMicros(generation), TimeUnit.NANOSECONDS.toMicros(compilationTime)); } // Instantiate serializer. diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ITDistributedTableTest.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ITDistributedTableTest.java index d6cec07..1cf219f 100644 --- a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ITDistributedTableTest.java +++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ITDistributedTableTest.java @@ -274,7 +274,7 @@ public class ITDistributedTableTest { * @param keysCnt Count of keys. */ public void partitionedTableView(Table view, int keysCnt) { - LOG.info("Test for Table view [keys=" + keysCnt + ']'); + LOG.info("Test for Table view [keys={}]", keysCnt); for (int i = 0; i < keysCnt; i++) { view.insert(view.tupleBuilder() @@ -381,7 +381,7 @@ public class ITDistributedTableTest { * @param keysCnt Count of keys. */ public void partitionedTableKVBinaryView(KeyValueBinaryView view, int keysCnt) { - LOG.info("Tes for Key-Value binary view [keys=" + keysCnt + ']'); + LOG.info("Tes for Key-Value binary view [keys={}]", keysCnt); for (int i = 0; i < keysCnt; i++) { view.putIfAbsent(