Skip to content

Conversation

@emeroad
Copy link
Member

@emeroad emeroad commented Jan 20, 2026

This pull request introduces a new utility class, KeyValueTokenizer, to standardize and simplify the parsing of key-value pairs across the codebase. The change replaces various manual string splitting logic with this centralized utility, improving code readability and reducing duplication. The update also includes comprehensive unit tests for the new utility and refactors several modules to use it.

Key changes include:

1. Introduction of KeyValueTokenizer utility:

  • Added KeyValueTokenizer in commons/src/main/java/com/navercorp/pinpoint/common/util/KeyValueTokenizer.java, providing methods for safe, null-aware key-value parsing with customizable factories for different value types.

2. Refactoring to use KeyValueTokenizer:

  • Replaced manual string splitting with KeyValueTokenizer in:
    • Dameng JDBC URL parsing and query parameter parsing (DamengJdbcUrlParser.java). [1] [2] [3]
    • gRPC Channelz socket lookup logic (ChannelzSocketLookup.java). [1] [2]
    • Exception trace query parameter filter parsing (ExceptionTraceQueryParameter.java). [1] [2]
    • Metric tag parsing utilities (TagUtils.java). [1] [2] [3]
    • Redis channel key parsing (RedisKVPubChannelProvider.java, RedisKVSubChannelProvider.java). [1] [2] [3] [4]

3. Testing improvements:

  • Added a dedicated test class, KeyValueTokenizerTest, with thorough unit tests covering various edge cases for the new utility.
  • Added a new test for empty value parsing in metric tag utilities (TagUtilsTest.java).

4. Minor related improvements:

  • Simplified and clarified tag string handling in TagUtils (e.g., improved JSON tag string cleaning).
  • Minor fix to ensure split methods in ServerMapHistogramController only split into two parts for node and link key parsing. [1] [2]

These changes collectively improve code maintainability, reduce the risk of subtle bugs in key-value parsing, and make the parsing logic more consistent across the project.

@emeroad emeroad added this to the 3.1.0 milestone Jan 20, 2026
@emeroad emeroad requested a review from Copilot January 20, 2026 11:13

This comment was marked as outdated.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

map.put(kv[0], StringUtils.EMPTY_STRING);
}
KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize(v, "=");
map.put(keyValue.getKey(), keyValue.getValue());
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential null pointer exception if KeyValueTokenizer.tokenize() returns null when the delimiter is not found. The code should check if keyValue is null before calling getKey() and getValue(), or handle the case where the query parameter doesn't contain '='.

Suggested change
map.put(keyValue.getKey(), keyValue.getValue());
if (keyValue == null) {
// Handle parameters without '=' by using the whole segment as key and an empty value
map.put(v, "");
} else {
map.put(keyValue.getKey(), keyValue.getValue());
}

Copilot uses AI. Check for mistakes.
if (words.length != 2) {
KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize(key, ":");
if (keyValue == null) {
throw new IllegalArgumentException("the key must contain period");
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message says 'the key must contain period' but the delimiter used is ':', not a period. The message should be corrected to match the actual delimiter.

Suggested change
throw new IllegalArgumentException("the key must contain period");
throw new IllegalArgumentException("the key must contain ':'");

Copilot uses AI. Check for mistakes.
@emeroad emeroad changed the title [#noissue] Refactor parsing logic to use KeyValueTokenizer. [#noissue] Refactor parsing logic to use KeyValueTokenizer Jan 21, 2026
@emeroad emeroad requested a review from Copilot January 21, 2026 01:59
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +8 to +43
class KeyValueTokenizerTest {

@Test
void tokenize() {
KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("key=value", "=");
assertEquals("key", keyValue.getKey());
assertEquals("value", keyValue.getValue());
}

@Test
void tokenize2() {
KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("key==value", "==");
assertEquals("key", keyValue.getKey());
assertEquals("value", keyValue.getValue());
}

@Test
void tokenize_emptyValue() {
KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("key=", "=");
assertEquals("key", keyValue.getKey());
assertEquals("", keyValue.getValue());
}

@Test
void tokenize_emptyKeyValue() {
KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("=", "=");
assertEquals("", keyValue.getKey());
assertEquals("", keyValue.getValue());
}

@Test
void tokenize_empty() {
KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("", "=");
Assertions.assertNull(keyValue);
}
} No newline at end of file
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The KeyValueTokenizerTest is missing a test case for when a null text parameter is passed to the tokenize method. According to line 32 of KeyValueTokenizer.java, the method will throw a NullPointerException if text is null. A test should be added to document this expected behavior, either verifying that a NullPointerException is thrown or that the method handles null gracefully.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +50
@Test
public void parseTagTest_emptyValue() {
Tag tag = TagUtils.parseTag("A:");

Assertions.assertEquals("A", tag.getName());
Assertions.assertEquals("", tag.getValue());
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test suite is missing a test case for when parseTag is called with a string that doesn't contain a colon delimiter. This is particularly important since the refactoring changed the behavior from the old implementation (which used StringUtils.split and handled missing delimiters by treating the entire string as the key with an empty value) to KeyValueTokenizer.tokenize (which returns null when the delimiter is not found). A test should verify the expected behavior in this case.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +29
* @param text the text to tokenize
* @param delimiter the delimiter separating key and value
* @param factory the factory used to create the token
* @param <T> the type of token to return
* @return the parsed token, or {@code null} if the delimiter is not found in the text
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The javadoc comment on line 29 states that the method returns "null if the delimiter is not found in the text", but this important behavior is not consistently documented or handled by all callers. Consider adding an @throws NullPointerException annotation for the text parameter, and potentially adding a note in the javadoc that callers should check for null return values.

Suggested change
* @param text the text to tokenize
* @param delimiter the delimiter separating key and value
* @param factory the factory used to create the token
* @param <T> the type of token to return
* @return the parsed token, or {@code null} if the delimiter is not found in the text
* @param text the text to tokenize; must not be {@code null}
* @param delimiter the delimiter separating key and value
* @param factory the factory used to create the token
* @param <T> the type of token to return
* @return the parsed token, or {@code null} if the delimiter is not found in the text; callers should
* check for a {@code null} return value
* @throws NullPointerException if {@code text} is {@code null}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant