From aef41a496d3c90b5f1e9e8e19e3f54c9269d6d73 Mon Sep 17 00:00:00 2001 From: mprokopchuk Date: Tue, 4 Aug 2026 18:00:35 -0700 Subject: [PATCH] Optimize ConfigKeyUtil.toMap by replacing regex with indexOf scan * Optimize ConfigKeyUtil.toMap by replacing regex with indexOf scan * Add private constructor and document parsing behavior in ConfigKeyUtil --- .../framework/config/ConfigKeyUtil.java | 41 ++++--- .../framework/config/ConfigKeyUtilTest.java | 110 ++++++++++++++++++ 2 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 framework/config/src/test/java/org/apache/cloudstack/framework/config/ConfigKeyUtilTest.java diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyUtil.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyUtil.java index e8ae35cf5377..d6acbe98007b 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyUtil.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyUtil.java @@ -18,10 +18,8 @@ import org.apache.commons.lang3.StringUtils; -import java.util.Arrays; +import java.util.HashMap; import java.util.Map; -import java.util.regex.Pattern; -import java.util.stream.Collectors; /** * Utility class that helps with configuration key manipulation. @@ -30,20 +28,14 @@ */ public final class ConfigKeyUtil { - /** - * Split by {@code ;} with optional space symbols (space, tab, new line, etc.) before and after. - */ - private static Pattern ENTRY_SEPARATOR_PATTERN = Pattern.compile("\\s*;\\s*"); - /** - * Split by {@code =} with optional space symbols (space, tab, new line, etc.) before and after. - */ - private static Pattern KEY_VALUE_SEPARATOR_PATTERN = Pattern.compile("\\s*=\\s*"); - private ConfigKeyUtil() { } /** * Convert configuration value of format {@code key1=value1;key2=value2;...} to {@link Map}. + *

+ * Parsing notes: surrounding whitespace is stripped from every key and value, entries with an empty + * key are skipped, and when the same key appears more than once the last occurrence wins. * * @param configValue configuration value string * @return configuration values map @@ -53,9 +45,26 @@ public static Map toMap(String configValue) { return Map.of(); } - return Arrays.stream(ENTRY_SEPARATOR_PATTERN.split(configValue)) - .map(pair -> KEY_VALUE_SEPARATOR_PATTERN.split(pair, 2)) - .filter(keyValue -> keyValue.length == 2) - .collect(Collectors.toMap(keyValue -> keyValue[0], keyValue -> keyValue[1])); + Map result = new HashMap<>(); + int start = 0; + int len = configValue.length(); + + // indexOf(char) is a JVM intrinsic (SIMD scan), avoiding Matcher allocation and regex engine overhead per call. + // strip() is a no-op when there is no surrounding whitespace, which is the common case for machine-generated values. + while (start < len) { + int end = configValue.indexOf(';', start); + if (end == -1) end = len; + + int eq = configValue.indexOf('=', start); + if (eq != -1 && eq < end) { + String key = configValue.substring(start, eq).strip(); + String value = configValue.substring(eq + 1, end).strip(); + if (!key.isEmpty()) { + result.put(key, value); + } + } + start = end + 1; + } + return result; } } diff --git a/framework/config/src/test/java/org/apache/cloudstack/framework/config/ConfigKeyUtilTest.java b/framework/config/src/test/java/org/apache/cloudstack/framework/config/ConfigKeyUtilTest.java new file mode 100644 index 000000000000..be044593868b --- /dev/null +++ b/framework/config/src/test/java/org/apache/cloudstack/framework/config/ConfigKeyUtilTest.java @@ -0,0 +1,110 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.framework.config; + +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ConfigKeyUtilTest { + + private static final String KEY = "key"; + private static final String VALUE = "value"; + private static final String KEY_1 = "key1"; + private static final String KEY_2 = "key2"; + private static final String VALUE_1 = "val1"; + private static final String VALUE_2 = "val2"; + + @Test + public void toMapNullReturnsEmpty() { + assertTrue(ConfigKeyUtil.toMap(null).isEmpty()); + } + + @Test + public void toMapEmptyStringReturnsEmpty() { + assertTrue(ConfigKeyUtil.toMap("").isEmpty()); + } + + @Test + public void toMapSingleEntry() { + Map result = ConfigKeyUtil.toMap(String.format("%s=%s", KEY, VALUE)); + assertEquals(1, result.size()); + assertEquals(VALUE, result.get(KEY)); + } + + @Test + public void toMapMultipleEntries() { + Map result = ConfigKeyUtil.toMap(String.format("%s=%s;%s=%s;key3=val3", KEY_1, VALUE_1, KEY_2, VALUE_2)); + assertEquals(3, result.size()); + assertEquals(VALUE_1, result.get(KEY_1)); + assertEquals(VALUE_2, result.get(KEY_2)); + assertEquals("val3", result.get("key3")); + } + + @Test + public void toMapWhitespaceAroundSeparators() { + Map result = ConfigKeyUtil.toMap(String.format("%s = %s ; %s = %s", KEY_1, VALUE_1, KEY_2, VALUE_2)); + assertEquals(2, result.size()); + assertEquals(VALUE_1, result.get(KEY_1)); + assertEquals(VALUE_2, result.get(KEY_2)); + } + + @Test + public void toMapTrailingSemicolon() { + Map result = ConfigKeyUtil.toMap(String.format("%s=%s;", KEY, VALUE)); + assertEquals(1, result.size()); + assertEquals(VALUE, result.get(KEY)); + } + + @Test + public void toMapValueContainsEquals() { + Map result = ConfigKeyUtil.toMap(String.format("%s=val=extra", KEY)); + assertEquals(1, result.size()); + assertEquals("val=extra", result.get(KEY)); + } + + @Test + public void toMapEmptyValueAllowed() { + Map result = ConfigKeyUtil.toMap(String.format("%s=", KEY)); + assertEquals(1, result.size()); + assertEquals("", result.get(KEY)); + } + + @Test + public void toMapEntryWithoutEqualsSkipped() { + Map result = ConfigKeyUtil.toMap(String.format("noequals;%s=%s", KEY, VALUE)); + assertEquals(1, result.size()); + assertEquals(VALUE, result.get(KEY)); + } + + @Test + public void toMapEmptyKeySkipped() { + Map result = ConfigKeyUtil.toMap(String.format("=%s;%s=val", VALUE, KEY)); + assertEquals(1, result.size()); + assertEquals("val", result.get(KEY)); + } + + @Test + public void toMapDuplicateKeyLastValueWins() { + Map result = ConfigKeyUtil.toMap(String.format("%s=first;%s=second", KEY, KEY)); + assertEquals(1, result.size()); + assertEquals("second", result.get(KEY)); + } +}