From a0111e17ae24d176f3969d75c54736713e654a99 Mon Sep 17 00:00:00 2001 From: AnandhaKrishnan2004 Date: Fri, 22 May 2026 23:19:26 +0530 Subject: [PATCH] refac:Parsing made strict and Unknown keys are ignored with debug/warn logs and Unit test is added --- .../com/asm/mja/args/AgentArgsParser.java | 292 ++++++++++++------ .../com/asm/mja/args/AgentArgsParserTest.java | 157 ++++++++-- 2 files changed, 330 insertions(+), 119 deletions(-) diff --git a/src/main/java/com/asm/mja/args/AgentArgsParser.java b/src/main/java/com/asm/mja/args/AgentArgsParser.java index 33a834c..e3f353f 100644 --- a/src/main/java/com/asm/mja/args/AgentArgsParser.java +++ b/src/main/java/com/asm/mja/args/AgentArgsParser.java @@ -3,135 +3,229 @@ import com.asm.mja.logging.LogLevel; import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; /** * Parses agent launch arguments into typed options. - * @author ashut - * @since 22-03-2026 + * + * Expected format: + * key=value,key=value + * + * Example: + * configFile=/tmp/config.yml, + * agentLogLevel=DEBUG, + * agentJarPath=/tmp/agent.jar + * + * Java 8 compatible implementation. + * + * @author anandh + * @since 22-05-2026 */ -public class AgentArgsParser { +public final class AgentArgsParser { + private static final Set SUPPORTED_KEYS = + new HashSet<>(Arrays.asList( + "configFile", + "agentLogFileDir", + "agentLogLevel", + "smtpProperties", + "agentJarPath" + )); + + private AgentArgsParser() { + } + + /** + * Parses and validates agent launch options. + */ public static AgentLaunchOptions parse(String agentArgs) { + + Map parsedArgs = parseArgs(agentArgs); + return new AgentLaunchOptions( - parseConfigFile(agentArgs), - parseAgentLogFileDir(agentArgs), - parseAgentLogLevel(agentArgs), - parseSmtpProperties(agentArgs), - parseAgentJarPath(agentArgs) + validateConfigFile(parsedArgs.get("configFile")), + validateAgentLogFileDir(parsedArgs.get("agentLogFileDir")), + validateAgentLogLevel(parsedArgs.get("agentLogLevel")), + validateSmtpProperties(parsedArgs.get("smtpProperties")), + validateAgentJarPath(parsedArgs.get("agentJarPath")) ); } - static String parseAgentLogFileDir(String agentArgs) { - String agentLogFileDir = null; - if (agentArgs != null) { - String[] args = agentArgs.split(","); - for (String arg : args) { - if (arg.contains("agentLogFileDir")) { - String[] prop = arg.split("="); - if (prop.length < 2) { - throw new IllegalArgumentException("Invalid arguments passed - " + arg); - } else { - agentLogFileDir = prop[1]; - File agentLogFileDirObj = new File(agentLogFileDir); - if (!agentLogFileDirObj.isDirectory() || !agentLogFileDirObj.exists()) { - throw new IllegalArgumentException("Agent logging doesn't exist or isn't a directory - " + agentLogFileDir); - } - } - } + /** + * Parses raw agent args into strict key=value pairs. + */ + private static Map parseArgs(String agentArgs) { + + Map parsed = new HashMap<>(); + + if (agentArgs == null || agentArgs.trim().isEmpty()) { + return parsed; + } + + String[] tokens = agentArgs.split(","); + + for (String token : tokens) { + + if (token == null || token.trim().isEmpty()) { + continue; + } + + token = token.trim(); + + String[] parts = token.split("=", 2); + + // malformed token + if (parts.length != 2) { + warn("Ignoring malformed token: " + token); + continue; } + + String key = parts[0].trim(); + String value = parts[1].trim(); + + // empty key + if (key.isEmpty()) { + warn("Ignoring token with empty key: " + token); + continue; + } + + // unknown key + if (!SUPPORTED_KEYS.contains(key)) { + warn("Ignoring unknown key: " + key); + continue; + } + + // duplicate key + if (parsed.containsKey(key)) { + throw new IllegalArgumentException( + "Duplicate argument detected: " + key + ); + } + + parsed.put(key, value); } - return agentLogFileDir; + + return parsed; } - static String parseAgentLogLevel(String agentArgs) { - String agentLogLevel = null; - if (agentArgs != null) { - String[] args = agentArgs.split(","); - for (String arg : args) { - if (arg.contains("agentLogLevel")) { - String[] prop = arg.split("="); - if (prop.length < 2) { - throw new IllegalArgumentException("Invalid arguments passed - " + arg); - } else { - agentLogLevel = prop[1]; - if (!isValidLogLevel(agentLogLevel)) { - throw new IllegalArgumentException("Invalid log level passed - " + agentLogLevel); - } - } - } - } + /** + * Validates agent log directory. + */ + static String validateAgentLogFileDir(String path) { + + if (path == null || path.trim().isEmpty()) { + return null; } - return agentLogLevel; + + File dir = new File(path); + + if (!dir.exists() || !dir.isDirectory()) { + throw new IllegalArgumentException( + "Agent logging directory doesn't exist or isn't a directory: " + path + ); + } + + return path; } - static String parseAgentJarPath(String agentArgs) { - String agentJarPath = null; - if (agentArgs != null) { - String[] args = agentArgs.split(","); - for (String arg : args) { - if (arg.contains("agentJarPath")) { - String[] prop = arg.split("="); - if (prop.length < 2) { - throw new IllegalArgumentException("Invalid arguments passed - " + arg); - } else { - agentJarPath = prop[1]; - File agentJarPathObj = new File(agentJarPath); - if (agentJarPathObj.isDirectory() || !agentJarPathObj.exists()) { - throw new IllegalArgumentException("Agent jar doesn't exist or is a directory - " + agentJarPath); - } - } - } - } + /** + * Validates agent log level. + */ + static String validateAgentLogLevel(String level) { + + if (level == null || level.trim().isEmpty()) { + return null; + } + + if (!isValidLogLevel(level)) { + throw new IllegalArgumentException( + "Invalid log level passed: " + level + ); } - return agentJarPath; + + return level; } - static String parseSmtpProperties(String agentArgs) { - String smtpPropertiesFile = null; - if (agentArgs != null) { - String[] args = agentArgs.split(","); - for (String arg : args) { - if (arg.contains("smtpProperties")) { - String[] prop = arg.split("="); - if (prop.length < 2) { - throw new IllegalArgumentException("Invalid arguments passed - " + arg); - } else { - smtpPropertiesFile = prop[1]; - } - } - } + /** + * Validates agent jar path. + */ + static String validateAgentJarPath(String path) { + + if (path == null || path.trim().isEmpty()) { + return null; } - return smtpPropertiesFile; + + File jarFile = new File(path); + + if (!jarFile.exists() || !jarFile.isFile()) { + throw new IllegalArgumentException( + "Agent jar doesn't exist or is invalid: " + path + ); + } + + return path; } - static String parseConfigFile(String agentArgs) { - String configFile = null; - if (agentArgs != null) { - String[] args = agentArgs.split(","); - for (String arg : args) { - if (arg.contains("configFile")) { - String[] prop = arg.split("="); - if (prop.length < 2) { - throw new IllegalArgumentException("Invalid arguments passed - " + arg); - } else { - configFile = prop[1]; - File configFileObj = new File(configFile); - if (!configFileObj.exists()) { - throw new IllegalArgumentException("Config file doesn't exist in the specified directory - " + configFile); - } - } - } - } + /** + * Validates SMTP properties path. + */ + static String validateSmtpProperties(String path) { + + if (path == null || path.trim().isEmpty()) { + return null; } - return configFile; + + return path; } + /** + * Validates config file path. + */ + static String validateConfigFile(String path) { + + if (path == null || path.trim().isEmpty()) { + return null; + } + + File configFile = new File(path); + + if (!configFile.exists() || !configFile.isFile()) { + throw new IllegalArgumentException( + "Config file doesn't exist or is invalid: " + path + ); + } + + return path; + } + + /** + * Validates log level enum. + */ public static boolean isValidLogLevel(String input) { + + if (input == null || input.trim().isEmpty()) { + return false; + } + for (LogLevel level : LogLevel.values()) { - if (level.name().equals(input)) { + + if (level.name().equalsIgnoreCase(input.trim())) { return true; } } + return false; } -} + + /** + * Temporary warning logger. + * Replace with proper logger later. + */ + private static void warn(String message) { + System.err.println("[AgentArgsParser] WARN: " + message); + } +} \ No newline at end of file diff --git a/src/test/java/com/asm/mja/args/AgentArgsParserTest.java b/src/test/java/com/asm/mja/args/AgentArgsParserTest.java index 968de4f..94eaa74 100644 --- a/src/test/java/com/asm/mja/args/AgentArgsParserTest.java +++ b/src/test/java/com/asm/mja/args/AgentArgsParserTest.java @@ -1,6 +1,7 @@ package com.asm.mja.args; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; import java.nio.file.Files; import java.nio.file.Path; @@ -11,14 +12,17 @@ /** * Unit tests for agent argument parsing behavior. - * @author ashut - * @since 22-03-2026 + * + * @author anandh + * @since 22-05-2026 */ public class AgentArgsParserTest { @Test void parseReturnsNullFieldsWhenAgentArgsMissing() { + AgentLaunchOptions options = AgentArgsParser.parse(null); + assertNull(options.getConfigFile()); assertNull(options.getAgentLogFileDir()); assertNull(options.getAgentLogLevel()); @@ -28,6 +32,7 @@ void parseReturnsNullFieldsWhenAgentArgsMissing() { @Test void parseReturnsAllFieldsWhenArgumentsAreValid() throws Exception { + Path configFile = Files.createTempFile("mja-config", ".yaml"); Path logDir = Files.createTempDirectory("mja-logdir"); Path agentJar = Files.createTempFile("mja-agent", ".jar"); @@ -40,6 +45,7 @@ void parseReturnsAllFieldsWhenArgumentsAreValid() throws Exception { ",agentJarPath=" + agentJar; AgentLaunchOptions options = AgentArgsParser.parse(args); + assertEquals(configFile.toString(), options.getConfigFile()); assertEquals(logDir.toString(), options.getAgentLogFileDir()); assertEquals("INFO", options.getAgentLogLevel()); @@ -48,41 +54,152 @@ void parseReturnsAllFieldsWhenArgumentsAreValid() throws Exception { } @Test - void parseReturnsAllFieldsWhenArgumentsAreValidTODO() throws Exception { + void parseIgnoresUnknownKeys() throws Exception { + Path configFile = Files.createTempFile("mja-config", ".yaml"); - Path logDir = Files.createTempDirectory("mja-logdir"); - Path agentJar = Files.createTempFile("mja-agent", ".jar"); String args = "configFile=" + configFile + - ",agentLogFileDir=" + logDir + - ",agentLogLevel=INFO" + - ",smtpProperties=C:\\smtp.properties" + - ",agentJarPath=" + agentJar; + ",unknownKey=test"; AgentLaunchOptions options = AgentArgsParser.parse(args); + assertEquals(configFile.toString(), options.getConfigFile()); - assertEquals(logDir.toString(), options.getAgentLogFileDir()); - assertEquals("INFO", options.getAgentLogLevel()); - assertEquals("C:\\smtp.properties", options.getSmtpProperties()); - assertEquals(agentJar.toString(), options.getAgentJarPath()); + assertNull(options.getAgentLogFileDir()); + assertNull(options.getAgentLogLevel()); } @Test - void parseAgentLogLevelThrowsForUnknownLevel() { + void parseIgnoresMalformedTokens() throws Exception { + + Path configFile = Files.createTempFile("mja-config", ".yaml"); + + String args = + "configFile=" + configFile + + ",badtoken" + + ",anotherbadtoken"; + + AgentLaunchOptions options = AgentArgsParser.parse(args); + + assertEquals(configFile.toString(), options.getConfigFile()); + } + + @Test + void parseThrowsForDuplicateKeys() { + IllegalArgumentException ex = assertThrows( IllegalArgumentException.class, - () -> AgentArgsParser.parseAgentLogLevel("agentLogLevel=NOT_A_LEVEL") + new Executable() { + @Override + public void execute() { + AgentArgsParser.validateAgentLogFileDir( + "C:\\does-not-exist\\logs" + ); + } + } + ); + + assertEquals( + "Agent logging directory doesn't exist or isn't a directory: C:\\does-not-exist\\logs", + ex.getMessage() ); - assertEquals("Invalid log level passed - NOT_A_LEVEL", ex.getMessage()); } @Test - void parseConfigFileThrowsWhenPathDoesNotExist() { + void validateAgentLogLevelThrowsForUnknownLevel() { + IllegalArgumentException ex = assertThrows( IllegalArgumentException.class, - () -> AgentArgsParser.parseConfigFile("configFile=C:\\does-not-exist\\m.yaml") + new Executable() { + @Override + public void execute() { + AgentArgsParser.validateAgentLogLevel("NOT_A_LEVEL"); + } + } + ); + + assertEquals( + "Invalid log level passed: NOT_A_LEVEL", + ex.getMessage() ); - assertEquals("Config file doesn't exist in the specified directory - C:\\does-not-exist\\m.yaml", ex.getMessage()); } -} + + @Test + void validateConfigFileThrowsWhenPathDoesNotExist() { + + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + new Executable() { + @Override + public void execute() { + AgentArgsParser.validateConfigFile( + "C:\\does-not-exist\\m.yaml" + ); + } + } + ); + + assertEquals( + "Config file doesn't exist or is invalid: C:\\does-not-exist\\m.yaml", + ex.getMessage() + ); + } + + @Test + void validateAgentJarPathThrowsWhenJarDoesNotExist() { + + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + new Executable() { + @Override + public void execute() { + AgentArgsParser.validateAgentJarPath( + "C:\\does-not-exist\\agent.jar" + ); + } + } + ); + + assertEquals( + "Agent jar doesn't exist or is invalid: C:\\does-not-exist\\agent.jar", + ex.getMessage() + ); + } + + @Test + void validateAgentLogFileDirThrowsWhenDirectoryDoesNotExist() { + + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + new Executable() { + @Override + public void execute() { + AgentArgsParser.validateAgentLogFileDir( + "C:\\does-not-exist\\logs" + ); + } + } + ); + + assertEquals( + "Agent logging directory doesn't exist or isn't a directory: C:\\does-not-exist\\logs", + ex.getMessage() + ); + } + + @Test + void isValidLogLevelReturnsTrueForValidLevels() { + + assertEquals(true, AgentArgsParser.isValidLogLevel("INFO")); + assertEquals(true, AgentArgsParser.isValidLogLevel("DEBUG")); + assertEquals(true, AgentArgsParser.isValidLogLevel("ERROR")); + } + + @Test + void isValidLogLevelReturnsFalseForInvalidLevels() { + + assertEquals(false, AgentArgsParser.isValidLogLevel("INVALID")); + assertEquals(false, AgentArgsParser.isValidLogLevel("")); + assertEquals(false, AgentArgsParser.isValidLogLevel(null)); + } +} \ No newline at end of file