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")