From 587d12d15296c38d8bb736e92dd3f2c14d476dff Mon Sep 17 00:00:00 2001 From: artsiomkorzun Date: Wed, 3 Jul 2024 13:13:01 +0200 Subject: [PATCH] improve substitutions --- ChangeLog.md | 6 + README.md | 4 +- .../deltix/gflog/core/util/PropertyUtil.java | 42 +------ .../gflog/core/util/StringSubstitution.java | 118 ++++++++++++++++++ .../core/util/StringSubstitutionTest.java | 83 ++++++++++++ 5 files changed, 210 insertions(+), 43 deletions(-) create mode 100644 gflog-core/src/main/java/com/epam/deltix/gflog/core/util/StringSubstitution.java create mode 100644 gflog-core/src/test/java/com/epam/deltix/gflog/core/util/StringSubstitutionTest.java diff --git a/ChangeLog.md b/ChangeLog.md index 6510f0d..cfaff8b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,11 @@ # Change Log +## 3.0.6 + +- Support default values in substitutions: ${some.property:-default.value} +- Support system properties and environment variables in substitutions: ${sys:some.property} and ${env:some.property} +- Support nested substitutions for default values: ${env:some.property:-${sys:some.property:-default.value}} + ## 3.0.5 - Support SLF4J 2.0.+. diff --git a/README.md b/README.md index 1933d43..e612228 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ Highly efficient garbage-free logging framework for Java 8+. Add the following dependencies to your project: ```gradle -implementation 'com.epam.deltix:gflog-api:3.0.5' -runtimeOnly 'com.epam.deltix:gflog-core:3.0.5' +implementation 'com.epam.deltix:gflog-api:3.0.6' +runtimeOnly 'com.epam.deltix:gflog-core:3.0.6' ``` Use the following sample to log a message: diff --git a/gflog-core/src/main/java/com/epam/deltix/gflog/core/util/PropertyUtil.java b/gflog-core/src/main/java/com/epam/deltix/gflog/core/util/PropertyUtil.java index e1960b9..8b67c69 100644 --- a/gflog-core/src/main/java/com/epam/deltix/gflog/core/util/PropertyUtil.java +++ b/gflog-core/src/main/java/com/epam/deltix/gflog/core/util/PropertyUtil.java @@ -9,9 +9,6 @@ public final class PropertyUtil { - private static final String SUBSTITUTION_START = "${"; - private static final char SUBSTITUTION_END = '}'; - private PropertyUtil() { } @@ -161,43 +158,6 @@ public static Duration toDuration(final String value) { } public static String substitute(final String value, final Properties properties) { - if (value.contains(SUBSTITUTION_START)) { - final StringBuilder builder = new StringBuilder(); - - final int length = value.length(); - int i = 0; - - while (true) { - final int j = value.indexOf(SUBSTITUTION_START, i); - if (j == -1) { - break; - } - - final int k = j + SUBSTITUTION_START.length(); - final int m = value.indexOf(SUBSTITUTION_END, k); - if (m == -1) { - break; - } - - if (i < j) { - builder.append(value, i, j); - } - - final String name = value.substring(k, m); - final String substitution = properties.getProperty(name, ""); - - builder.append(substitution); - i = m + 1; - } - - if (i < length) { - builder.append(value, i, length); - } - - return builder.toString(); - } - - return value; + return new StringSubstitution(properties, System.getProperties(), System.getenv()).substitute(value); } - } diff --git a/gflog-core/src/main/java/com/epam/deltix/gflog/core/util/StringSubstitution.java b/gflog-core/src/main/java/com/epam/deltix/gflog/core/util/StringSubstitution.java new file mode 100644 index 0000000..b475796 --- /dev/null +++ b/gflog-core/src/main/java/com/epam/deltix/gflog/core/util/StringSubstitution.java @@ -0,0 +1,118 @@ +package com.epam.deltix.gflog.core.util; + +import java.util.Map; +import java.util.Properties; + + +public final class StringSubstitution { + + private static final String SYSTEM_PROPERTY_PREFIX = "sys:"; + private static final String ENVIRONMENT_VARIABLE_PREFIX = "env:"; + + private final Properties defaultProperties; + private final Properties systemProperties; + private final Map environmentVariables; + + public StringSubstitution(final Properties defaultProperties, + final Properties systemProperties, + final Map environmentVariables) { + this.defaultProperties = defaultProperties; + this.systemProperties = systemProperties; + this.environmentVariables = environmentVariables; + } + + public String substitute(final String text) { + final StringBuilder builder = new StringBuilder(); + + for (int index = 0; index < text.length(); ) { + final int position = text.indexOf("${", index); + + if (position < 0) { + builder.append(text, index, text.length()); + break; + } + + builder.append(text, index, position); + index = substitute(text, position, builder); + } + + return builder.toString(); + } + + private int substitute(final String source, int index, final StringBuilder target) { + final int end = source.length(); + final int start = index; + + index += 2; + + while (index < end) { + char c = source.charAt(index); + + if (c == '}') { + final String key = source.substring(start + 2, index); + final String value = lookup(key); + + if (value == null) { + target.append("${").append(key).append(":#UNRESOLVED#").append("}"); + } else { + target.append(value); + } + + return index + 1; + } + + if (c == ':' && index + 1 < end && source.charAt(index + 1) == '-') { + final String key = source.substring(start + 2, index); + final String value = lookup(key); + + final int length = target.length(); + final int mid = index + 2; + + index = mid; + + while (index < end) { + c = source.charAt(index); + + if (c == '}') { + if (value != null) { + target.setLength(length); + target.append(value); + } + + return index + 1; + } + + if (c == '$' && index + 1 < end && source.charAt(index + 1) == '{') { + index = substitute(source, index, target); + continue; + } + + target.append(c); + index++; + } + + target.insert(length, source, start, mid); + return end; + } + + index++; + } + + target.append(source, start, end); + return index; + } + + private String lookup(final String key) { + if (key.startsWith(SYSTEM_PROPERTY_PREFIX)) { + final String propertyKey = key.substring(SYSTEM_PROPERTY_PREFIX.length()); + return systemProperties.getProperty(propertyKey); + } + + if (key.startsWith(ENVIRONMENT_VARIABLE_PREFIX)) { + final String environmentKey = key.substring(ENVIRONMENT_VARIABLE_PREFIX.length()); + return environmentVariables.get(environmentKey); + } + + return defaultProperties.getProperty(key); + } +} \ No newline at end of file diff --git a/gflog-core/src/test/java/com/epam/deltix/gflog/core/util/StringSubstitutionTest.java b/gflog-core/src/test/java/com/epam/deltix/gflog/core/util/StringSubstitutionTest.java new file mode 100644 index 0000000..36392f4 --- /dev/null +++ b/gflog-core/src/test/java/com/epam/deltix/gflog/core/util/StringSubstitutionTest.java @@ -0,0 +1,83 @@ +package com.epam.deltix.gflog.core.util; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + +public class StringSubstitutionTest { + + @Test + public void testDefaults() { + final Properties defaults = new Properties(); + defaults.put("a", "A"); + defaults.put("b", "B"); + defaults.put("c", "C"); + + final Properties systems = new Properties(); + final Map environments = new HashMap<>(); + final StringSubstitution substitution = new StringSubstitution(defaults, systems, environments); + final Map mapping = new LinkedHashMap<>(); + + mapping.put("12345", "12345"); + mapping.put("${a} ${b} ${c}", "A B C"); + mapping.put("${a:-10} ${b:-${c:-100500}}", "A B"); + mapping.put("${missing:-5} + ${missing:-10}", "5 + 10"); + mapping.put("${missing:-${a}}", "A"); + mapping.put("${missing:-${missing2:-${b}}}", "B"); + mapping.put("${missing:-${missing2:-${missing3:-${c}}}}", "C"); + mapping.put("${a:-5", "${a:-5"); + mapping.put(" ${a:-${b} ", " ${a:-B "); + mapping.put(" ${a:-${b:-${c ", " ${a:-${b:-${c "); + mapping.put(" ${a:-${b:-${c} ", " ${a:-${b:-C "); + mapping.put(" ${a:-${b:-${c}} ", " ${a:-B "); + mapping.put("${missing}", "${missing:#UNRESOLVED#}"); + mapping.put("${missing:-1 ${a} 2 ${missing2} 3}", "1 A 2 ${missing2:#UNRESOLVED#} 3"); + mapping.put("${missing:-}", ""); + + for (final Map.Entry entry : mapping.entrySet()) { + final String text = entry.getKey(); + final String expected = entry.getValue(); + final String actual = substitution.substitute(text); + + Assert.assertEquals(expected, actual); + } + } + + @Test + public void testEnvironmentAndSystemSubstitution() { + final Properties defaults = new Properties(); + defaults.put("a", "1"); + defaults.put("b", "2"); + defaults.put("c", "3"); + + final Properties systems = new Properties(); + systems.put("a", "4"); + systems.put("b", "5"); + systems.put("c", "6"); + + final Map environments = new HashMap<>(); + environments.put("a", "7"); + environments.put("b", "8"); + environments.put("c", "9"); + + final StringSubstitution substitution = new StringSubstitution(defaults, systems, environments); + final Map mapping = new LinkedHashMap<>(); + + mapping.put("${a} ${sys:a} ${env:a}", "1 4 7"); + mapping.put("${missing:-${b}} ${missing:-${sys:b}} ${missing:-${env:b}}", "2 5 8"); + mapping.put("${sys:missing:-${b}} ${sys:missing:-${sys:b}} ${sys:missing:-${env:b}}", "2 5 8"); + mapping.put("${env:missing:-${b}} ${env:missing:-${sys:b}} ${env:missing:-${env:b}}", "2 5 8"); + + for (final Map.Entry entry : mapping.entrySet()) { + final String text = entry.getKey(); + final String expected = entry.getValue(); + final String actual = substitution.substitute(text); + + Assert.assertEquals(expected, actual); + } + } +}