diff --git a/.agents/java-kotlin-conversion.md b/.agents/java-kotlin-conversion.md index 2b40835e2..8a95fe192 100644 --- a/.agents/java-kotlin-conversion.md +++ b/.agents/java-kotlin-conversion.md @@ -52,3 +52,5 @@ * Preserve the content of original comments but convert the format and markup. * Always use braces `{}` for `if`, `for`, and `while` statements. * If the converted function has more than two `return` statements, annotate it with `@Suppress("ReturnCount")`. + * Keep inline comments from the original code. + diff --git a/dependencies.md b/dependencies.md index b91ba5a51..a120c7e2a 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-logging-fixtures:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-fixtures:2.0.0-SNAPSHOT.258` ## Runtime ## Compile, tests, and tooling @@ -724,12 +724,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-grpc-context:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-grpc-context:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -1553,12 +1553,12 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jul-backend:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-jul-backend:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -2366,12 +2366,12 @@ This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-default-platform:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-jvm-default-platform:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -3187,12 +3187,12 @@ This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-jul-backend-grpc-context:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-jvm-jul-backend-grpc-context:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -4035,12 +4035,12 @@ This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-jul-backend-std-context:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-jvm-jul-backend-std-context:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -4875,12 +4875,12 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-log4j2-backend-std-context:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-jvm-log4j2-backend-std-context:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -5715,12 +5715,12 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-slf4j-jdk14-backend-std-context:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-jvm-slf4j-jdk14-backend-std-context:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -6563,12 +6563,12 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-slf4j-reload4j-backend-std-context:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-jvm-slf4j-reload4j-backend-std-context:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -7415,12 +7415,12 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-log4j2-backend:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-log4j2-backend:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -8244,12 +8244,12 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging:2.0.0-SNAPSHOT.258` ## Runtime ## Compile, tests, and tooling @@ -8981,12 +8981,12 @@ This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:spine-logging-testlib:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine.tools:spine-logging-testlib:2.0.0-SNAPSHOT.258` ## Runtime ## Compile, tests, and tooling @@ -9706,12 +9706,12 @@ This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-middleware:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-middleware:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -10519,12 +10519,12 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-platform-generator:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-platform-generator:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -11308,12 +11308,12 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-probe-backend:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-probe-backend:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.auto.service. **Name** : auto-service-annotations. **Version** : 1.1.1. @@ -12296,12 +12296,12 @@ This report was generated on **Sat Jul 05 10:51:48 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-smoke-test:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-smoke-test:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.18.3. @@ -13169,12 +13169,12 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-std-context:2.0.0-SNAPSHOT.257` +# Dependencies of `io.spine:spine-logging-std-context:2.0.0-SNAPSHOT.258` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -13986,4 +13986,4 @@ This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Jul 05 10:51:49 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Sun Jul 13 18:32:38 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/BraceStyleParameter.java b/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/BraceStyleParameter.java deleted file mode 100644 index 4058d1a28..000000000 --- a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/BraceStyleParameter.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. - * - * Licensed 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 - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.logging.jvm.parameter; - -import io.spine.logging.jvm.backend.FormatChar; -import io.spine.logging.jvm.backend.FormatOptions; -import io.spine.logging.jvm.backend.FormatType; - -import java.text.MessageFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.Locale; - -/** - * A parameter implementation to mimic the formatting of brace style placeholders (ie, "{n}"). - * - * @see - * Original Java code of Google Flogger for historical context. - */ -public class BraceStyleParameter extends Parameter { - - // Format options to mimic how '{0}' is formatted for numbers (i.e. like "%,d" or "%,f"). - private static final FormatOptions WITH_GROUPING = - FormatOptions.of(FormatOptions.FLAG_SHOW_GROUPING, FormatOptions.UNSET, - FormatOptions.UNSET); - - // Message formatter for fallback cases where '{n}' formats sufficiently differently to any - // available printf specifier that we must preformat the result ourselves. - // TODO: Get the Locale from the Platform class for better i18n support. - private static final MessageFormat prototypeMessageFormatter = - new MessageFormat("{0}", Locale.ROOT); - - /** Cache parameters with indices 0-9 to cover the vast majority of cases. */ - private static final int MAX_CACHED_PARAMETERS = 10; - - /** Map of the most common default general parameters (corresponds to %s, %d, %f etc...). */ - private static final BraceStyleParameter[] DEFAULT_PARAMETERS; - - static { - DEFAULT_PARAMETERS = new BraceStyleParameter[MAX_CACHED_PARAMETERS]; - for (var index = 0; index < MAX_CACHED_PARAMETERS; index++) { - DEFAULT_PARAMETERS[index] = new BraceStyleParameter(index); - } - } - - /** - * Returns a {@link Parameter} representing a plain "brace style" placeholder "{n}". - * Note that a cached value may be returned. - * - * @param index - * the index of the argument to be processed. - * - * @return the immutable, thread safe parameter instance. - */ - public static BraceStyleParameter of(int index) { - return index < MAX_CACHED_PARAMETERS - ? DEFAULT_PARAMETERS[index] - : new BraceStyleParameter(index); - } - - private BraceStyleParameter(int index) { - super(FormatOptions.getDefault(), index); - } - - @Override - protected void accept(ParameterVisitor visitor, Object value) { - // Special cases which MessageFormat treats specially (oddly Calendar is not a special case). - if (FormatType.INTEGRAL.canFormat(value)) { - visitor.visit(value, FormatChar.DECIMAL, WITH_GROUPING); - } else if (FormatType.FLOAT.canFormat(value)) { - // Technically floating point formatting via {0} differs from "%,f", but as "%,f" results in - // more precision it seems better to mimic "%,f" rather than discard both precision and type - // information by calling visitPreformatted(). - visitor.visit(value, FormatChar.FLOAT, WITH_GROUPING); - } else if (value instanceof Date) { - // MessageFormat is not thread safe, so we always clone(). - var formatted = ((MessageFormat) prototypeMessageFormatter.clone()) - .format(new Object[]{value}, new StringBuffer(), null /* field position */) - .toString(); - visitor.visitPreformatted(value, formatted); - } else if (value instanceof Calendar) { - visitor.visitDateTime(value, DateTimeFormat.DATETIME_FULL, getFormatOptions()); - } else { - visitor.visit(value, FormatChar.STRING, getFormatOptions()); - } - } - - @Override - public String getFormat() { - return "%s"; - } -} diff --git a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/DateTimeParameter.java b/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/DateTimeParameter.java deleted file mode 100644 index f7efbbcaa..000000000 --- a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/DateTimeParameter.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. - * - * Licensed 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 - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.logging.jvm.parameter; - -import io.spine.logging.jvm.backend.FormatOptions; - -/** - * A parameter for formatting date/time arguments. - * - *

- * This class is immutable and thread-safe, as per the Parameter contract. - * - * @see - * Original Java code of Google Flogger for historical context. - */ -public final class DateTimeParameter extends Parameter { - /** - * Returns a {@link Parameter} representing the given formatting options of the specified - * date/time formatting character. Note that a cached value may be returned. - * - * @param format specifier for the specific date/time formatting to be applied. - * @param options the validated formatting options. - * @param index the argument index. - * @return the immutable, thread safe parameter instance. - */ - public static Parameter of(DateTimeFormat format, FormatOptions options, int index) { - return new DateTimeParameter(options, index, format); - } - - private final DateTimeFormat format; - private final String formatString; - - private DateTimeParameter(FormatOptions options, int index, DateTimeFormat format) { - super(options, index); - this.format = format; - this.formatString = - options - .appendPrintfOptions(new StringBuilder("%")) - .append(options.shouldUpperCase() ? 'T' : 't') - .append(format.getChar()) - .toString(); - } - - @Override - protected void accept(ParameterVisitor visitor, Object value) { - visitor.visitDateTime(value, format, getFormatOptions()); - } - - @Override - public String getFormat() { - return formatString; - } -} diff --git a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/Parameter.java b/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/Parameter.java deleted file mode 100644 index 926168074..000000000 --- a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/Parameter.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. - * - * Licensed 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 - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.logging.jvm.parameter; - -import io.spine.logging.jvm.backend.FormatOptions; - -/** - * An abstract representation of a parameter for a message template. - * - *

- * Note that this is implemented as a class (rather than via an interface) because it is very - * helpful to have explicit checks for the index values and count to ensure we can calculate - * reliable low bounds for the number of arguments a template can accept. - * - *

- * Note that all subclasses of Parameter must be immutable and thread safe. - * - * @see - * Original Java code of Google Flogger for historical context. - */ -public abstract class Parameter { - - private final int index; - private final FormatOptions options; - - /** - * Constructs a parameter to format an argument using specified formatting options. - * - * @param options - * the format options for this parameter. - * - * @param index - * the index of the argument processed by this parameter. - */ - protected Parameter(FormatOptions options, int index) { - if (options == null) { - throw new IllegalArgumentException("format options cannot be null"); - } - if (index < 0) { - throw new IllegalArgumentException("invalid index: " + index); - } - this.index = index; - this.options = options; - } - - /** Returns the index of the argument to be processed by this parameter. */ - public final int getIndex() { - return index; - } - - /** Returns the formatting options. */ - protected final FormatOptions getFormatOptions() { - return options; - } - - public final void accept(ParameterVisitor visitor, Object[] args) { - if (getIndex() < args.length) { - var value = args[getIndex()]; - if (value != null) { - accept(visitor, value); - } else { - visitor.visitNull(); - } - } else { - visitor.visitMissing(); - } - } - - protected abstract void accept(ParameterVisitor visitor, Object value); - - /** - * Returns the printf format string specified for this parameter (eg, "%d" or "%tc"). - */ - public abstract String getFormat(); -} diff --git a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/ParameterVisitor.java b/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/ParameterVisitor.java deleted file mode 100644 index 4e9117f20..000000000 --- a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/ParameterVisitor.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. - * - * Licensed 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 - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.logging.jvm.parameter; - -import io.spine.logging.jvm.backend.FormatChar; -import io.spine.logging.jvm.backend.FormatOptions; - -/** - * A visitor of log message arguments, dispatched by {@code Parameter} instances. - * - * @see - * Original Java code of Google Flogger for historical context. - */ -// TODO: When all other refactoring done, rename to ArgumentVisitor -public interface ParameterVisitor { - /** - * Visits a log message argument with formatting specified by {@code %s}, {@code %d} etc... - * - *

- * Note that this method may still visit arguments which represent date/time values if the format - * is not explicit (e.g. {@code log("time=%s", dateTime)}). - * - * @param value the non-null log message argument. - * @param format the printf format specifier. - * @param options formatting options. - */ - void visit(Object value, FormatChar format, FormatOptions options); - - /** - * Visits a date/time log message argument with formatting specified by {@code %t} or similar. - * - *

- * Note that because this method is called based on the specified format (and not the argument - * type) it may visit arguments whose type is not a known date/time value. This is necessary to - * permit new date/time types to be supported by different logging backends (e.g. JodaTime). - * - * @param value the non-null log message argument. - * @param format the date/time format specifier. - * @param options formatting options. - */ - void visitDateTime(Object value, DateTimeFormat format, FormatOptions options); - - /** - * Visits a log message argument for which formatting has already occurred. This method is only - * invoked when non-printf message formatting is used (e.g. brace style formatting). - * - *

- * This method is intended for use by {@code Parameter} implementations which describe formatting - * rules which cannot by represented by either {@link FormatChar} or {@link DateTimeFormat}. This - * method discards formatting and type information, and the visitor implementation may choose to - * reexamine the type of the original argument if doing structural logging. - * - * @param value the original non-null log message argument. - * @param formatted the formatted representation of the argument - */ - void visitPreformatted(Object value, String formatted); - - /** - * Visits a missing argument. This method is called when there is no corresponding value for the - * parameter's argument index. - */ - void visitMissing(); - - /** Visits a null argument. */ - void visitNull(); -} diff --git a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/SimpleParameter.java b/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/SimpleParameter.java deleted file mode 100644 index fef5c96af..000000000 --- a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/SimpleParameter.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. - * - * Licensed 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 - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.logging.jvm.parameter; - -import io.spine.logging.jvm.backend.FormatChar; -import io.spine.logging.jvm.backend.FormatOptions; - -import java.util.Collections; -import java.util.EnumMap; -import java.util.Map; - -import static io.spine.logging.jvm.util.Checks.checkNotNull; - -/** - * A simple, single argument, parameter which can format arguments according to the rules specified - * by {@link FormatChar}. - * - *

- * This class is immutable and thread safe, as per the Parameter contract. - * - * @see - * Original Java code of Google Flogger for historical context. - */ -public final class SimpleParameter extends Parameter { - - /** Cache parameters with indices 0-9 to cover the vast majority of cases. */ - private static final int MAX_CACHED_PARAMETERS = 10; - - /** Map of the most common default general parameters (corresponds to %s, %d, %f etc...). */ - private static final Map DEFAULT_PARAMETERS; - - static { - Map map = new EnumMap<>(FormatChar.class); - for (var fc : FormatChar.values()) { - map.put(fc, createParameterArray(fc)); - } - DEFAULT_PARAMETERS = Collections.unmodifiableMap(map); - } - - /** Helper to make reusable default parameter instances for the commonest indices. */ - private static SimpleParameter[] createParameterArray(FormatChar formatChar) { - var parameters = new SimpleParameter[MAX_CACHED_PARAMETERS]; - for (var index = 0; index < MAX_CACHED_PARAMETERS; index++) { - parameters[index] = new SimpleParameter(index, formatChar, FormatOptions.getDefault()); - } - return parameters; - } - - /** - * Returns a {@link Parameter} representing the given formatting options of the specified - * formatting character. Note that a cached value may be returned. - * - * @param index - * the index of the argument to be processed. - * - * @param formatChar - * the basic formatting type. - * - * @param options - * additional formatting options. - * - * @return the immutable, thread safe parameter instance. - */ - public static SimpleParameter of(int index, FormatChar formatChar, FormatOptions options) { - // We can safely test FormatSpec with '==' because the factory methods always return the default - // instance if applicable (and the class has no visible constructors). - if (index < MAX_CACHED_PARAMETERS && options.isDefault()) { - return DEFAULT_PARAMETERS.get(formatChar)[index]; - } - return new SimpleParameter(index, formatChar, options); - } - - private final FormatChar formatChar; - private final String formatString; - - private SimpleParameter(int index, FormatChar formatChar, FormatOptions options) { - super(options, index); - this.formatChar = checkNotNull(formatChar, "format char"); - // TODO: Consider special case for hex strings where options are common (HexParameter?). - this.formatString = options.isDefault() - ? formatChar.getDefaultFormatString() - : buildFormatString(options, formatChar); - } - - // Visible for testing. - static String buildFormatString(FormatOptions options, FormatChar formatChar) { - // The format char is guaranteed to be a lower-case ASCII character, so can be made upper case - // by simply subtracting 0x20 (or clearing the 6th bit). - char c = formatChar.getChar(); - c = options.shouldUpperCase() ? (char) (c & ~0x20) : c; - return options.appendPrintfOptions(new StringBuilder("%")) - .append(c) - .toString(); - } - - @Override - protected void accept(ParameterVisitor visitor, Object value) { - visitor.visit(value, formatChar, getFormatOptions()); - } - - @Override - public String getFormat() { - return formatString; - } -} diff --git a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/package-info.java b/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/package-info.java deleted file mode 100644 index c1bb4c3a4..000000000 --- a/jvm/middleware/src/main/java/io/spine/logging/jvm/parameter/package-info.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. - * - * Licensed 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 - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Contains different types of message template parameters. - * - * @see - * Original Java code of Google Flogger for historical context. - */ -@CheckReturnValue -package io.spine.logging.jvm.parameter; - -import com.google.errorprone.annotations.CheckReturnValue; diff --git a/jvm/middleware/src/main/java/io/spine/logging/jvm/parser/DefaultPrintfMessageParser.java b/jvm/middleware/src/main/java/io/spine/logging/jvm/parser/DefaultPrintfMessageParser.java index edaecb7cd..ad27ea8ef 100644 --- a/jvm/middleware/src/main/java/io/spine/logging/jvm/parser/DefaultPrintfMessageParser.java +++ b/jvm/middleware/src/main/java/io/spine/logging/jvm/parser/DefaultPrintfMessageParser.java @@ -31,7 +31,7 @@ import io.spine.logging.jvm.parameter.DateTimeFormat; import io.spine.logging.jvm.parameter.DateTimeParameter; import io.spine.logging.jvm.parameter.Parameter; -import io.spine.logging.jvm.parameter.ParameterVisitor; +import io.spine.logging.jvm.parameter.ArgumentVisitor; import io.spine.logging.jvm.parameter.SimpleParameter; import static io.spine.logging.jvm.backend.FormatOptions.FLAG_LEFT_ALIGN; @@ -84,7 +84,7 @@ public int parsePrintfTerm( throw ParseException.withBounds("invalid format specifier", message, termStart, termEnd); } - parameter = SimpleParameter.of(index, formatChar, options); + parameter = SimpleParameter.of(formatChar, options, index); } else if (typeChar == 't' || typeChar == 'T') { if (!options.validate(FLAG_LEFT_ALIGN | FLAG_UPPER_CASE, false)) { throw ParseException.withBounds( @@ -122,7 +122,7 @@ private static Parameter wrapHexParameter(final FormatOptions options, int index // %h / %H is really just %x / %X on the hashcode. return new Parameter(options, index) { @Override - protected void accept(ParameterVisitor visitor, Object value) { + protected void accept(ArgumentVisitor visitor, Object value) { visitor.visit(value.hashCode(), FormatChar.HEX, getFormatOptions()); } diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/BaseMessageFormatter.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/BaseMessageFormatter.kt index 56f885e0d..b102ad2ff 100644 --- a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/BaseMessageFormatter.kt +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/BaseMessageFormatter.kt @@ -36,14 +36,14 @@ import io.spine.logging.jvm.backend.FormatChar.STRING import io.spine.logging.jvm.backend.FormatOptions.Companion.FLAG_UPPER_CASE import io.spine.logging.jvm.parameter.DateTimeFormat import io.spine.logging.jvm.parameter.Parameter -import io.spine.logging.jvm.parameter.ParameterVisitor +import io.spine.logging.jvm.parameter.ArgumentVisitor import io.spine.logging.jvm.parser.MessageBuilder import java.util.* /** * The default formatter for log messages and arguments. * - * This formatter can be overridden to modify the behaviour of the [ParameterVisitor] + * This formatter can be overridden to modify the behaviour of the [ArgumentVisitor] * methods, but this is not expected to be common. Most logger backends will only ever need * to use [appendFormattedMessage]. * @@ -59,7 +59,7 @@ protected constructor( context: TemplateContext, protected val args: Array, protected val out: StringBuilder -) : MessageBuilder(context), ParameterVisitor { +) : MessageBuilder(context), ArgumentVisitor { /** * The start of the next literal subsection of the message that needs processing. @@ -77,7 +77,7 @@ protected constructor( return out } - override fun visit(value: Any?, format: FormatChar, options: FormatOptions) { + override fun visit(value: Any, format: FormatChar, options: FormatOptions) { if (format.type.canFormat(value)) { out.appendFormatted(value, format, options) } else { @@ -85,7 +85,7 @@ protected constructor( } } - override fun visitDateTime(value: Any?, format: DateTimeFormat, options: FormatOptions) { + override fun visitDateTime(value: Any, format: DateTimeFormat, options: FormatOptions) { if (value is Date || value is Calendar || value is Long) { val formatString = options .appendPrintfOptions(StringBuilder("%")) @@ -98,7 +98,7 @@ protected constructor( } } - override fun visitPreformatted(value: Any?, formatted: String) { + override fun visitPreformatted(value: Any, formatted: String) { // For unstructured logging we just use the pre-formatted string. out.append(formatted) } @@ -178,7 +178,7 @@ private fun StringBuilder.appendFormatted( if (!options.isDefault) { var chr = format.char if (options.shouldUpperCase()) { - // Clear 6th bit to convert lower case ASCII to upper case. + // Clear the 6th bit to convert lower case ASCII to upper case. chr = chr.uppercaseChar() } formatString = options.appendPrintfOptions(StringBuilder("%")) diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/FormatOptions.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/FormatOptions.kt index 7bdab48fa..af92e18e2 100644 --- a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/FormatOptions.kt +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/FormatOptions.kt @@ -26,6 +26,8 @@ package io.spine.logging.jvm.backend +import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe import io.spine.logging.jvm.parser.ParseException /** @@ -33,7 +35,7 @@ import io.spine.logging.jvm.parser.ParseException * * This class is immutable and thread-safe. * - * @property flags The flag bits for this options instance. + * @property flags The flag bits for this instance. * Where possible the per-flag methods `shouldXxx()` should be preferred for code clarity, * but for efficiency and when testing multiple flags values at the same time, * this method is useful. @@ -50,6 +52,8 @@ import io.spine.logging.jvm.parser.ParseException * @see * Original Java code of Google Flogger for historical context. */ +@Immutable +@ThreadSafe @Suppress("TooManyFunctions") public class FormatOptions private constructor( public val flags: Int, diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/Platform.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/Platform.kt index 04ab60048..39a33d11d 100644 --- a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/Platform.kt +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/backend/Platform.kt @@ -34,6 +34,8 @@ import io.spine.logging.jvm.util.RecursionDepth import java.util.logging.Level import java.util.concurrent.TimeUnit.MILLISECONDS import com.google.common.base.Preconditions.checkNotNull +import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe import java.lang.reflect.InvocationTargetException import kotlin.time.ExperimentalTime @@ -224,6 +226,7 @@ public abstract class Platform { /** * API for determining the logging class and log statement sites, return from [getCallerFinder]. + * * These classes are immutable and thread-safe. * * This functionality is not provided directly by the `Platform` API because doing so would @@ -252,6 +255,8 @@ public abstract class Platform { * can delay any actual work until its methods are called. For example, if any additional state is * required in the implementation, it can be held via a "lazy holder" to defer initialization. */ + @Immutable + @ThreadSafe public abstract class LogCallerFinder { /** diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/context/LogLevelMap.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/context/LogLevelMap.kt index ca219fd23..feee50b03 100644 --- a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/context/LogLevelMap.kt +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/context/LogLevelMap.kt @@ -27,6 +27,8 @@ package io.spine.logging.jvm.context import com.google.errorprone.annotations.CanIgnoreReturnValue +import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe import java.util.* import java.util.logging.Level @@ -40,6 +42,8 @@ import java.util.logging.Level * @see [Original Java code of Google Flogger](https://github.com/google/flogger/blob/cb9e836a897d36a78309ee8badf5cad4e6a2d3d8/api/src/main/java/com/google/common/flogger/context/LogLevelMap.java) * for historical context. */ +@Immutable +@ThreadSafe public class LogLevelMap private constructor(map: Map, defaultLevel: Level) { private val trie: SegmentTrie = SegmentTrie.create(map, '.', defaultLevel) diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/context/SegmentTrie.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/context/SegmentTrie.kt index fd2d9837c..a4fcf2439 100644 --- a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/context/SegmentTrie.kt +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/context/SegmentTrie.kt @@ -1,5 +1,7 @@ package io.spine.logging.jvm.context +import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe import java.util.* import kotlin.math.min @@ -23,6 +25,8 @@ import kotlin.math.min * - Allows `null` values, but `null` stops parent matching. * - Immutable and thread-safe after creation. */ +@Immutable +@ThreadSafe internal abstract class SegmentTrie(private val defaultValue: T) { /** diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/ArgumentVisitor.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/ArgumentVisitor.kt new file mode 100644 index 000000000..d45d500fd --- /dev/null +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/ArgumentVisitor.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. + * + * Licensed 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.logging.jvm.parameter + +import io.spine.logging.jvm.backend.FormatChar +import io.spine.logging.jvm.backend.FormatOptions + +/** + * A visitor of log message arguments, dispatched by {@code Parameter} instances. + * + * @see + * Original Java code of Google Flogger for historical context. + */ +public interface ArgumentVisitor { + + /** + * Visits a log message argument with formatting specified by `%s`, `%d` etc. + * + * Note that this method may still visit arguments which represent date/time values if + * the format is not explicit (e.g. `log("time=%s", dateTime)`). + * + * @param value The non-null log message argument. + * @param format The printf format specifier. + * @param options formatting options. + */ + public fun visit(value: Any, format: FormatChar, options: FormatOptions) + + /** + * Visits a date/time log message argument with formatting specified by `%t` or similar. + * + * Note that because this method is called based on the specified format (and not + * the argument type) it may visit arguments whose type is not a known date/time value. + * This is necessary to permit new date/time types to be supported by different logging + * backends (e.g., JodaTime). + * + * @param value The non-null log message argument. + * @param format The date/time format specifier. + * @param options Formatting options. + */ + public fun visitDateTime(value: Any, format: DateTimeFormat, options: FormatOptions) + + /** + * Visits a log message argument for which formatting has already occurred. + * + * This function is only invoked when non-`printf` message formatting is used + * (e.g., brace style formatting). + * + * The function is intended for use by [Parameter] implementations which describe formatting + * rules which cannot by represented by either [FormatChar] or [DateTimeFormat]. + * This method discards formatting and type information, and the visitor implementation may + * choose to reexamine the type of the original argument if doing structural logging. + * + * @param value The original non-null log message argument. + * @param formatted The formatted representation of the argument + */ + public fun visitPreformatted(value: Any, formatted: String) + + /** + * Visits a missing argument. + * + * This method is called when there is no corresponding value for + * the parameter's argument index. + */ + public fun visitMissing() + + /** + * Visits a null argument. + */ + public fun visitNull() +} diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/BraceStyleParameter.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/BraceStyleParameter.kt new file mode 100644 index 000000000..209f9094b --- /dev/null +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/BraceStyleParameter.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. + * + * Licensed 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.logging.jvm.parameter + +import io.spine.logging.jvm.backend.FormatChar +import io.spine.logging.jvm.backend.FormatOptions +import io.spine.logging.jvm.backend.FormatType +import java.text.MessageFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale + +/** + * A parameter implementation to mimic the formatting of brace style placeholders (i.e., `"{n}"`). + * + * @see + * Original Java code of Google Flogger for historical context. + */ +public class BraceStyleParameter private constructor(index: Int) : + Parameter(FormatOptions.getDefault(), index) { + + override fun accept(visitor: ArgumentVisitor, value: Any) { + // Special cases which MessageFormat treats specially + // (oddly, `Calendar` is not a special case). + when { + FormatType.INTEGRAL.canFormat(value) -> + visitor.visit(value, FormatChar.DECIMAL, withGroupings) + FormatType.FLOAT.canFormat(value) -> + // Technically floating point formatting via {0} differs from "%,f", but as "%,f" + // results in more precision it seems better to mimic "%,f" rather than discard + // both precision and type information by calling visitPreformatted(). + visitor.visit(value, FormatChar.FLOAT, withGroupings) + value is Date -> { + // MessageFormat is not thread safe, so we always `clone()`. + val formatted = (prototypeMessageFormatter.clone() as MessageFormat) + .format(arrayOf(value), StringBuffer(), null) + .toString() + visitor.visitPreformatted(value, formatted) + } + value is Calendar -> + visitor.visitDateTime(value, DateTimeFormat.DATETIME_FULL, formatOptions) + else -> + visitor.visit(value, FormatChar.STRING, formatOptions) + } + } + + override val format: String = "%s" + + public companion object { + + /** + * Cache parameters with indexes 0-9 to cover the vast majority of cases. + */ + private const val MAX_CACHED_PARAMETERS = 10 + + /** + * Map of the most common default general parameters (corresponds to `%s`, `%d`, `%f` etc.). + */ + private val defaultParameters = Array(MAX_CACHED_PARAMETERS) { BraceStyleParameter(it) } + + /** + * Format options to mimic how '{0}' is formatted for numbers + * (i.e., like `"%,d"` or `"%,f"`). + */ + private val withGroupings = FormatOptions.of( + FormatOptions.FLAG_SHOW_GROUPING, + FormatOptions.UNSET, + FormatOptions.UNSET + ) + + /** + * Message formatter for fallback cases where `{n}` formats sufficiently differently to any + * available `printf` specifier that we must preformat the result ourselves. + */ + // TODO: Get the Locale from the Platform class for better i18n support. + private val prototypeMessageFormatter = MessageFormat("{0}", Locale.ROOT) + + + /** + * Returns a [Parameter] representing a plain "brace style" placeholder `"{n}"`. + * + * Note that a cached value may be returned. + * + * @param index The index of the argument to be processed. + * @return the immutable, thread safe parameter instance. + */ + @JvmStatic + public fun of(index: Int): BraceStyleParameter = + if (index < MAX_CACHED_PARAMETERS) { + defaultParameters[index] + } else { + BraceStyleParameter(index) + } + } +} diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/DateTimeFormat.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/DateTimeFormat.kt index 2b44d1299..a659258cb 100644 --- a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/DateTimeFormat.kt +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/DateTimeFormat.kt @@ -34,7 +34,7 @@ import java.util.Date * Supported date/time sub-format characters for the `%t/%T` formatting pattern. * * **WARNING:** Many date/time format specifiers use the system default time-zone for formatting - * [Date] or [Long] arguments. This makes it non system-portable, and its use is heavily + * [Date] or [Long] arguments. This makes it non-system-portable, and its use is heavily * discouraged with non-[Calendar] arguments. * * @see diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/DateTimeParameter.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/DateTimeParameter.kt new file mode 100644 index 000000000..7bb0c2da1 --- /dev/null +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/DateTimeParameter.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. + * + * Licensed 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.logging.jvm.parameter + +import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe +import io.spine.logging.jvm.backend.FormatOptions + +/** + * A parameter for formatting date/time arguments. + * + * This class is immutable and thread-safe, as per the [Parameter] contract. + * + * @property specifier The specifier for the specific date/time formatting to be applied. + * @param options The validated formatting options. + * @param index The argument index. + * + * @see + * Original Java code of Google Flogger for historical context. + */ +@Immutable +@ThreadSafe +public class DateTimeParameter private constructor( + private val specifier: DateTimeFormat, + index: Int, + options: FormatOptions +) : Parameter(options, index) { + + private val formatString: String = buildString { + append('%') + formatOptions.appendPrintfOptions(this) + append(if (formatOptions.shouldUpperCase()) 'T' else 't') + append(specifier.char) + } + + override fun accept(visitor: ArgumentVisitor, value: Any) { + visitor.visitDateTime(value, specifier, formatOptions) + } + + override val format: String = formatString + + public companion object { + + /** + * Returns a [Parameter] representing the given formatting options of the specified + * date/time formatting character. + * + * Note that a cached value may be returned. + * + * @param format The specifier for the specific date/time formatting to be applied. + * @param options The validated formatting options. + * @param index The argument index. + * @return the immutable, thread-safe parameter instance. + */ + @JvmStatic + public fun of(format: DateTimeFormat, options: FormatOptions, index: Int): Parameter = + DateTimeParameter(format, index, options) + } +} diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/Parameter.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/Parameter.kt new file mode 100644 index 000000000..a4f0a84c6 --- /dev/null +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/Parameter.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. + * + * Licensed 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.logging.jvm.parameter + +import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe +import io.spine.logging.jvm.backend.FormatOptions + +/** + * An abstract representation of a parameter for a message template. + * + * Note that this is implemented as a class (rather than via an interface) because it is very + * helpful to have explicit checks for the index values and count to ensure we can calculate + * reliable low bounds for the number of arguments a template can accept. + * + * Note that all subclasses of Parameter must be immutable and thread-safe. + * + * @property formatOptions The format options for this parameter. + * @property index The index of the argument processed by this parameter. + * @constructor Constructs a parameter to format an argument using specified formatting options. + * + * @see + * Original Java code of Google Flogger for historical context. + */ +@Immutable +@ThreadSafe +public abstract class Parameter protected constructor( + public val formatOptions: FormatOptions, + public val index: Int +) { + init { + require(index >= 0) { "Invalid index: $index" } + } + + /** + * Accepts a visitor and processes the argument at the parameter's index. + * + * This method handles three cases: + * 1. If the argument exists and is not `null`, processes it using [accept]. + * 2. If the argument exists but is `null`, calls [ArgumentVisitor.visitNull]. + * 3. If no argument exists at the index, calls [ArgumentVisitor.visitMissing]. + * + * @param visitor The visitor that will process the argument + * @param args The array of arguments to process + */ + public fun accept(visitor: ArgumentVisitor, args: Array) { + if (index < args.size) { + val value = args[index] + if (value != null) { + accept(visitor, value) + } else { + visitor.visitNull() + } + } else { + visitor.visitMissing() + } + } + + + /** + * Processes a non-null argument value using the given visitor. + * + * The implementing classes implement this function to define how specific parameter types + * process their arguments. The implementation should use appropriate visitor methods + * based on the parameter type and formatting requirements. + * + * @param visitor The visitor that will process the argument value + * @param value The argument value to be processed + */ + protected abstract fun accept(visitor: ArgumentVisitor, value: Any) + + /** + * The `printf` format string specified for this parameter (e.g., `"%d"` or `"%tc"`). + */ + public abstract val format: String +} diff --git a/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/SimpleParameter.kt b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/SimpleParameter.kt new file mode 100644 index 000000000..31dae67aa --- /dev/null +++ b/jvm/middleware/src/main/kotlin/io/spine/logging/jvm/parameter/SimpleParameter.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. + * + * Licensed 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.logging.jvm.parameter + +import com.google.common.annotations.VisibleForTesting +import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe +import io.spine.logging.jvm.backend.FormatChar +import io.spine.logging.jvm.backend.FormatOptions + +/** + * A simple, single argument, parameter which can format arguments according + * to the rules specified by [FormatChar]. + * + * This class is immutable and thread-safe, as per the [Parameter] contract. + * + * @property formatChar The basic formatting type. + * @param index The index of the argument to be processed. + * @param options Additional formatting options. + * + * @see + * Original Java code of Google Flogger for historical context. + */ +@Immutable +@ThreadSafe +public class SimpleParameter private constructor( + private val formatChar: FormatChar, + options: FormatOptions, + index: Int +) : Parameter(options, index) { + + private val formatString: String = if (formatOptions.isDefault) { + formatChar.defaultFormatString + } else { + buildFormatString(formatOptions, formatChar) + } + + override fun accept(visitor: ArgumentVisitor, value: Any) { + visitor.visit(value, formatChar, formatOptions) + } + + override val format: String = formatString + + public companion object { + + /** + * Cache parameters with indexes 0-9 to cover the vast majority of cases. + */ + private const val MAX_CACHED_PARAMETERS = 10 + + /** + * The map of the most common default general parameters + * like `%s`, `%d`, `%f`, etc. + */ + private val defaultParameters: Map> + + init { + val map = mutableMapOf>() + for (fc in FormatChar.entries) { + map[fc] = createParameterArray(fc) + } + defaultParameters = map + } + + private fun createParameterArray(formatChar: FormatChar): Array = + Array(MAX_CACHED_PARAMETERS) { + SimpleParameter(formatChar, FormatOptions.getDefault(), it) + } + + /** + * Returns a [Parameter] representing the given formatting options of the specified + * formatting character. + * + * Note that a cached value may be returned. + * + * @param formatChar The basic formatting type. + * @param index The index of the argument to be processed. + * @param options Additional formatting options. + */ + @JvmStatic + public fun of(formatChar: FormatChar, options: FormatOptions, index: Int): Parameter { + // We can safely test FormatSpec with '==' because the factory methods always return + // the default instance if applicable (and the class has no visible constructors). + return if (index < MAX_CACHED_PARAMETERS && options.isDefault) { + defaultParameters[formatChar]!![index] + } else { + SimpleParameter(formatChar, options, index) + } + } + + @JvmStatic + @VisibleForTesting + @Suppress("MagicNumber") + internal fun buildFormatString(options: FormatOptions, formatChar: FormatChar): String { + // The format char is guaranteed to be a lower-case ASCII character, + // so can be made upper case by simply subtracting 0x20 (or clearing the 6th bit). + var c = formatChar.char + if (options.shouldUpperCase()) { + c = (c.code and 0xDF).toChar() + } + return options.appendPrintfOptions(StringBuilder("%")).append(c).toString() + } + } +} diff --git a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parameter/ParameterSpec.kt b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parameter/ParameterSpec.kt index b88a8b47a..474fc0952 100644 --- a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parameter/ParameterSpec.kt +++ b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parameter/ParameterSpec.kt @@ -42,13 +42,6 @@ import org.junit.jupiter.api.assertThrows @DisplayName("`Parameter` should") internal class ParameterSpec { - @Test - fun `not accept nullable options`() { - assertThrows { - TestParameter(null, 0) - } - } - @Test fun `not accept a negative index`() { assertThrows { @@ -72,13 +65,12 @@ internal class ParameterSpec { } } -private class TestParameter(options: FormatOptions?, index: Int) : Parameter(options, index) { +private class TestParameter(options: FormatOptions, index: Int) : Parameter(options, index) { - override fun accept(visitor: ParameterVisitor?, value: Any?) { + override fun accept(visitor: ArgumentVisitor, value: Any) { throw UnsupportedOperationException() } - override fun getFormat(): String { - throw UnsupportedOperationException() - } + override val format: String + get() = throw UnsupportedOperationException() } diff --git a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parameter/SimpleParameterSpec.kt b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parameter/SimpleParameterSpec.kt index a7ce6cf01..2c51cdf89 100644 --- a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parameter/SimpleParameterSpec.kt +++ b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parameter/SimpleParameterSpec.kt @@ -34,7 +34,7 @@ import io.spine.logging.jvm.backend.FormatChar.DECIMAL import io.spine.logging.jvm.backend.FormatChar.FLOAT import io.spine.logging.jvm.backend.FormatChar.HEX import io.spine.logging.jvm.backend.FormatChar.STRING -import io.spine.logging.jvm.parameter.SimpleParameter.buildFormatString +import io.spine.logging.jvm.parameter.SimpleParameter.Companion.buildFormatString import io.spine.logging.jvm.parser.ParseException import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeSameInstanceAs @@ -55,10 +55,10 @@ internal class SimpleParameterSpec { @Test fun `return the same instance for the same chars without options up to first 10 indices`() { - for (char: FormatChar in FormatChar.values()) { + for (char: FormatChar in FormatChar.entries) { repeat(10) { index -> - val instance1 = SimpleParameter.of(index, char, options) - val instance2 = SimpleParameter.of(index, char, options) + val instance1 = SimpleParameter.of(char, options, index) + val instance2 = SimpleParameter.of(char, options, index) instance1 shouldBeSameInstanceAs instance2 } } @@ -69,23 +69,23 @@ internal class SimpleParameterSpec { @Test fun `for different indices`() { - val zeroIndex = SimpleParameter.of(0, DECIMAL, options) - val firstIndex = SimpleParameter.of(1, DECIMAL, options) + val zeroIndex = SimpleParameter.of(DECIMAL, options, 0) + val firstIndex = SimpleParameter.of(DECIMAL, options, 1) zeroIndex shouldNotBeSameInstanceAs firstIndex } @Test fun `for different format chars`() { - val decimalChar = SimpleParameter.of(0, DECIMAL, options) - val floatChar = SimpleParameter.of(0, FLOAT, options) + val decimalChar = SimpleParameter.of(DECIMAL, options, 0) + val floatChar = SimpleParameter.of(FLOAT, options, 0) decimalChar shouldNotBeSameInstanceAs floatChar } @Test fun `for different formatting options`() { val customOptions = FormatOptions.parse("-10", 0, 3, false) - val withCustomOptions = SimpleParameter.of(0, FLOAT, customOptions) - val withDefaultOptions = SimpleParameter.of(0, DECIMAL, options) + val withCustomOptions = SimpleParameter.of(FLOAT, customOptions, 0) + val withDefaultOptions = SimpleParameter.of(DECIMAL, options, 0) withCustomOptions shouldNotBeSameInstanceAs withDefaultOptions } } diff --git a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/DefaultBraceStyleMessageParserSpec.kt b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/DefaultBraceStyleMessageParserSpec.kt index 943fdc185..db5cb751c 100644 --- a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/DefaultBraceStyleMessageParserSpec.kt +++ b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/DefaultBraceStyleMessageParserSpec.kt @@ -31,7 +31,7 @@ import io.spine.logging.jvm.backend.FormatOptions import io.spine.logging.jvm.backend.FormatOptions.Companion.FLAG_SHOW_GROUPING import io.spine.logging.jvm.backend.FormatOptions.Companion.UNSET import io.spine.logging.jvm.parser.given.MemoizingMessageBuilder -import io.spine.logging.jvm.parser.given.MemoizingParameterVisitor +import io.spine.logging.jvm.parser.given.MemoizingArgumentVisitor import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import org.junit.jupiter.api.DisplayName @@ -68,7 +68,7 @@ internal class DefaultBraceStyleMessageParserSpec { // Now visit the parameter and verify the expected callback occurred. val param = memoizingBuilder.param - val memoizingVisitor = MemoizingParameterVisitor() + val memoizingVisitor = MemoizingArgumentVisitor() param.accept(memoizingVisitor, arrayOf("Answer: ", 42)) with(memoizingVisitor) { diff --git a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/DefaultPrintfMessageParserSpec.kt b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/DefaultPrintfMessageParserSpec.kt index 4a599f60b..bf0fd3b54 100644 --- a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/DefaultPrintfMessageParserSpec.kt +++ b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/DefaultPrintfMessageParserSpec.kt @@ -28,7 +28,7 @@ package io.spine.logging.jvm.parser import io.spine.logging.jvm.backend.FormatChar import io.spine.logging.jvm.parser.given.MemoizingMessageBuilder -import io.spine.logging.jvm.parser.given.MemoizingParameterVisitor +import io.spine.logging.jvm.parser.given.MemoizingArgumentVisitor import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import org.junit.jupiter.api.DisplayName @@ -60,7 +60,7 @@ internal class DefaultPrintfMessageParserSpec { // Now visit the parameter and remember its state. val param = memoizingBuilder.param - val memoizingVisitor = MemoizingParameterVisitor() + val memoizingVisitor = MemoizingArgumentVisitor() param.accept(memoizingVisitor, arrayOf("Answer: ", 42.0)) // Recover the remembered arguments and check that the right formatting was done. diff --git a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/given/ParserTestEnv.kt b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/given/ParserTestEnv.kt index 4fd0288eb..ad9928bb6 100644 --- a/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/given/ParserTestEnv.kt +++ b/jvm/middleware/src/test/kotlin/io/spine/logging/jvm/parser/given/ParserTestEnv.kt @@ -31,7 +31,7 @@ import io.spine.logging.jvm.backend.FormatOptions import io.spine.logging.jvm.backend.TemplateContext import io.spine.logging.jvm.parameter.DateTimeFormat import io.spine.logging.jvm.parameter.Parameter -import io.spine.logging.jvm.parameter.ParameterVisitor +import io.spine.logging.jvm.parameter.ArgumentVisitor import io.spine.logging.jvm.parser.MessageBuilder import io.spine.logging.jvm.parser.MessageParser import io.spine.logging.jvm.parser.ParseException @@ -56,10 +56,11 @@ import org.junit.jupiter.api.assertThrows internal class FakeParameter(index: Int, private val details: String) : Parameter(FormatOptions.getDefault(), index) { - override fun accept(visitor: ParameterVisitor, value: Any) = + override fun accept(visitor: ArgumentVisitor, value: Any) = throw UnsupportedOperationException("not used in test") - override fun getFormat(): String = throw UnsupportedOperationException("not used in test") + override val format: String + get() = throw UnsupportedOperationException("not used in test") override fun toString(): String = if (details.isEmpty()) "$index" else "$index:$details" } @@ -123,7 +124,7 @@ internal class MemoizingMessageBuilder(parser: MessageParser) : /** * Remembers arguments of [visit] method's last invocation. */ -internal class MemoizingParameterVisitor : ParameterVisitor { +internal class MemoizingArgumentVisitor : ArgumentVisitor { lateinit var value: Any lateinit var format: FormatChar @@ -135,9 +136,9 @@ internal class MemoizingParameterVisitor : ParameterVisitor { this.options = options } - override fun visitDateTime(value: Any?, format: DateTimeFormat?, options: FormatOptions?) = Unit + override fun visitDateTime(value: Any, format: DateTimeFormat, options: FormatOptions) = Unit - override fun visitPreformatted(value: Any?, formatted: String?) = Unit + override fun visitPreformatted(value: Any, formatted: String) = Unit override fun visitMissing() = Unit diff --git a/logging/src/commonMain/kotlin/io/spine/logging/context/LogLevelMap.kt b/logging/src/commonMain/kotlin/io/spine/logging/context/LogLevelMap.kt index 4352aa613..0b2934d64 100644 --- a/logging/src/commonMain/kotlin/io/spine/logging/context/LogLevelMap.kt +++ b/logging/src/commonMain/kotlin/io/spine/logging/context/LogLevelMap.kt @@ -26,6 +26,8 @@ package io.spine.logging.context +import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe import io.spine.logging.Level import io.spine.logging.toLoggerName import kotlin.reflect.KClass @@ -38,6 +40,8 @@ import kotlin.reflect.KClass * * Implementing classes are expected to be immutable and thread-safe. */ +@Immutable +@ThreadSafe public interface LogLevelMap { /** diff --git a/logging/src/jvmMain/kotlin/io/spine/logging/context/LoggingContextFactory.kt b/logging/src/jvmMain/kotlin/io/spine/logging/context/LoggingContextFactory.kt index 7819239b6..57a4c1597 100644 --- a/logging/src/jvmMain/kotlin/io/spine/logging/context/LoggingContextFactory.kt +++ b/logging/src/jvmMain/kotlin/io/spine/logging/context/LoggingContextFactory.kt @@ -28,6 +28,7 @@ package io.spine.logging.context import com.google.errorprone.annotations.CheckReturnValue import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe import io.spine.logging.JvmMetadataKey import io.spine.logging.Level import io.spine.logging.MetadataKey @@ -45,6 +46,7 @@ import io.spine.logging.jvm.context.ScopedLoggingContext as JScopedLoggingContex * The class is immutable and thread-safe. */ @Immutable +@ThreadSafe @CheckReturnValue internal actual object LoggingContextFactory { diff --git a/platforms/jvm-default-platform/src/main/java/io/spine/logging/backend/system/StackBasedCallerFinder.java b/platforms/jvm-default-platform/src/main/java/io/spine/logging/backend/system/StackBasedCallerFinder.java index c5f3d873f..d5d7fe287 100644 --- a/platforms/jvm-default-platform/src/main/java/io/spine/logging/backend/system/StackBasedCallerFinder.java +++ b/platforms/jvm-default-platform/src/main/java/io/spine/logging/backend/system/StackBasedCallerFinder.java @@ -26,6 +26,8 @@ package io.spine.logging.backend.system; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.ThreadSafe; import io.spine.logging.jvm.AbstractLogger; import io.spine.logging.jvm.JvmLogSite; import io.spine.logging.jvm.JvmLogSites; @@ -41,6 +43,8 @@ * * @see Original Java code of Google Flogger for historical context. */ +@Immutable +@ThreadSafe public final class StackBasedCallerFinder extends LogCallerFinder { private static final LogCallerFinder INSTANCE = new StackBasedCallerFinder(); diff --git a/platforms/jvm-default-platform/src/test/kotlin/io/spine/logging/backend/system/given/DefaultPlatformTestEnv.kt b/platforms/jvm-default-platform/src/test/kotlin/io/spine/logging/backend/system/given/DefaultPlatformTestEnv.kt index 2ec58b6a6..188fa1035 100644 --- a/platforms/jvm-default-platform/src/test/kotlin/io/spine/logging/backend/system/given/DefaultPlatformTestEnv.kt +++ b/platforms/jvm-default-platform/src/test/kotlin/io/spine/logging/backend/system/given/DefaultPlatformTestEnv.kt @@ -26,6 +26,8 @@ package io.spine.logging.backend.system.given +import com.google.errorprone.annotations.Immutable +import com.google.errorprone.annotations.ThreadSafe import io.spine.logging.jvm.backend.BackendFactory import io.spine.logging.jvm.backend.Clock import io.spine.logging.jvm.AbstractLogger @@ -61,6 +63,8 @@ internal class FixedTime : Clock() { /** * No-op implementation of [Platform.LogCallerFinder]. */ +@Immutable +@ThreadSafe internal class NoOpCallerFinder : Platform.LogCallerFinder() { /** diff --git a/pom.xml b/pom.xml index 1c0de2755..3212c6f1f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine spine-logging -2.0.0-SNAPSHOT.257 +2.0.0-SNAPSHOT.258 2015 diff --git a/version.gradle.kts b/version.gradle.kts index bce25df1f..5b8d86719 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -24,4 +24,4 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -val versionToPublish: String by extra("2.0.0-SNAPSHOT.257") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.258")