From a8fc49896f556fa12fca9e661cda950eba38c073 Mon Sep 17 00:00:00 2001 From: piyush kumar sadangi Date: Sun, 18 Aug 2024 15:14:39 -0700 Subject: [PATCH] Resolves #153: 1. Addressed review comments of the workflow by adding a new parameter as output of default proj list 2. Earlier code was using file system to load class, had to replace with inputStream as jars need classLoader --- .../extractor/CheckstyleExampleExtractor.java | 78 ++++++++++++++-- .../example/extractor/ConfigSerializer.java | 91 ++++++++++++++++--- .../java/com/example/extractor/MainTest.java | 16 +++- .../example/extractor/MainsLauncherTest.java | 46 +++++++--- 4 files changed, 197 insertions(+), 34 deletions(-) diff --git a/extractor/src/main/java/com/example/extractor/CheckstyleExampleExtractor.java b/extractor/src/main/java/com/example/extractor/CheckstyleExampleExtractor.java index 54eda52c7..00fc207b5 100644 --- a/extractor/src/main/java/com/example/extractor/CheckstyleExampleExtractor.java +++ b/extractor/src/main/java/com/example/extractor/CheckstyleExampleExtractor.java @@ -20,6 +20,8 @@ package com.example.extractor; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -76,7 +78,7 @@ public final class CheckstyleExampleExtractor { /** * Number of expected arguments when processing a single input file. */ - private static final int SINGLE_INPUT_FILE_ARG_COUNT = 4; + private static final int SINGLE_INPUT_FILE_ARG_COUNT = 5; /** * Index of the "--input-file" flag in the argument array. @@ -93,6 +95,16 @@ public final class CheckstyleExampleExtractor { */ private static final int OUTPUT_FILE_PATH_INDEX = 3; + /** + * Index of the output file path in the argument array. + */ + private static final int PROJECT_OUTPUT_PATH_INDEX = 4; + + /** + * The buffer size for reading and writing files. + */ + private static final int BUFFER_SIZE = 1024; + /** * Private constructor to prevent instantiation of this utility class. */ @@ -119,8 +131,14 @@ public static void main(final String[] args) throws Exception { && "--input-file".equals(args[INPUT_FILE_FLAG_INDEX])) { // New functionality: process single input file final String inputFilePath = args[INPUT_FILE_PATH_INDEX]; - final String outputFilePath = args[OUTPUT_FILE_PATH_INDEX]; - processInputFile(Paths.get(inputFilePath), Paths.get(outputFilePath)); + final String configOutputPath = args[OUTPUT_FILE_PATH_INDEX]; + final String projectsOutputPath = args[PROJECT_OUTPUT_PATH_INDEX]; + + // Process input file and generate config + processInputFile(Paths.get(inputFilePath), Paths.get(configOutputPath)); + + // Output default projects list + outputDefaultProjectsList(projectsOutputPath); } else { // Functionality: process all examples @@ -137,13 +155,37 @@ public static void main(final String[] args) throws Exception { } } + /** + * Writes the default projects list to the specified file. + * + * @param outputPath the file path to write the list. + * @throws IllegalStateException if I/O error occurs while readin or writing to file + */ + public static void outputDefaultProjectsList(final String outputPath) { + try (InputStream inputStream = CheckstyleExampleExtractor.class + .getResourceAsStream("/list-of-projects.properties"); + OutputStream outputStream = Files.newOutputStream(Path.of(outputPath))) { + + final byte[] buffer = new byte[BUFFER_SIZE]; + int length = inputStream.read(buffer); + while (length > 0) { + outputStream.write(buffer, 0, length); + length = inputStream.read(buffer); + } + } + catch (IOException ex) { + throw new IllegalStateException("Error outputting default projects list", ex); + } + } + /** * Processes an input file and generates an output file. * * @param inputFile The path to the input file * @param outputFile The path to the output file * @throws Exception If an error occurs during processing - * @throws IOException if the argument is invalid. + * @throws IllegalArgumentException if the argument is invalid. + * @throws IOException if resource not found */ public static void processInputFile( final Path inputFile, @@ -155,11 +197,33 @@ public static void processInputFile( throw new IOException("Input file does not exist: " + inputFile); } - // Get the template file path and serialize the configuration - final String templateFilePath = getTemplateFilePathForInputFile(inputFile.toString()); + // Parse the input file to determine if it's a TreeWalker check + final TestInputConfiguration testInputConfiguration = + InlineConfigParser.parse(inputFile.toString()); + final List modules = + testInputConfiguration.getChildrenModules(); + + if (modules.isEmpty()) { + throw new IllegalArgumentException("No modules found in the input file"); + } + + final ModuleInputConfiguration mainModule = modules.get(0); + final String moduleName = mainModule.getModuleName(); + final boolean isTreeWalker = ConfigSerializer.isTreeWalkerCheck(moduleName); + + // Get the template file name based on whether it's a TreeWalker check or not + final String templateFileName; + if (isTreeWalker) { + templateFileName = "config-template-treewalker.xml"; + } + else { + templateFileName = "config-template-non-treewalker.xml"; + } + + // Serialize the configuration final String generatedContent = ConfigSerializer.serializeNonXmlConfigToString( inputFile.toString(), - templateFilePath + templateFileName ); // Write the generated content to the output file diff --git a/extractor/src/main/java/com/example/extractor/ConfigSerializer.java b/extractor/src/main/java/com/example/extractor/ConfigSerializer.java index 4eeb25e92..e8e8c68cf 100644 --- a/extractor/src/main/java/com/example/extractor/ConfigSerializer.java +++ b/extractor/src/main/java/com/example/extractor/ConfigSerializer.java @@ -24,6 +24,10 @@ package com.example.extractor; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -33,6 +37,7 @@ import java.util.List; import java.util.Map; import java.util.logging.Logger; +import java.util.stream.Collectors; import com.puppycrawl.tools.checkstyle.DefaultConfiguration; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; @@ -93,30 +98,36 @@ private ConfigSerializer() { * Serializes a configuration to a string from a non-XML format input file. * * @param inputFilePath Path to the input file - * @param templateFilePath Path to the template file + * @param templateFileName Path to the template file * @return Serialized configuration as a string * @throws IllegalArgumentException If no modules are found in the input file * @throws Exception If an unexpected error occurs */ public static String serializeNonXmlConfigToString( final String inputFilePath, - final String templateFilePath) throws Exception { - final TestInputConfiguration testInputConfiguration = - InlineConfigParser.parse(inputFilePath); - final List modules = testInputConfiguration.getChildrenModules(); - - if (modules.isEmpty()) { - throw new IllegalArgumentException("No modules found in the input file"); + final String templateFileName) throws Exception { + if (inputFilePath == null || templateFileName == null) { + throw new IllegalArgumentException( + "Input file path and template resource name must not be null" + ); } - final ModuleInputConfiguration mainModule = modules.get(0); + final ModuleInputConfiguration mainModule = parseAndValidateInputFile(inputFilePath); + final String moduleName = extractSimpleModuleName(mainModule.getModuleName()); final Map properties = mainModule.getNonDefaultProperties(); - final String template = Files.readString(Path.of(templateFilePath), StandardCharsets.UTF_8); + LOGGER.info("Reading template resource: " + templateFileName); + final String template = readResourceAsString(templateFileName); + if (template == null || template.isEmpty()) { + throw new IllegalArgumentException( + "Failed to read template resource: " + templateFileName + ); + } final Configuration moduleConfig = createConfigurationFromModule(moduleName, properties); final boolean isTreeWalker = isTreeWalkerCheck(mainModule.getModuleName()); + final String baseIndent; if (isTreeWalker) { baseIndent = TREE_WALKER_INDENT; @@ -126,7 +137,65 @@ public static String serializeNonXmlConfigToString( } final String moduleContent = buildSingleModuleContent(moduleConfig, baseIndent); - return TemplateProcessor.replacePlaceholders(template, moduleContent, isTreeWalker); + return TemplateProcessor.replacePlaceholders(template, moduleContent, isTreeWalker) + "\n"; + } + + /** + * Parses and validates the input file to extract the main module configuration. + * + * @param inputFilePath the path to the input file. + * @return the main {@link ModuleInputConfiguration} extracted from the input file. + * @throws IllegalArgumentException if parsing fails or no valid modules are found. + * @throws Exception if an unexpected error occurs. + */ + private static ModuleInputConfiguration parseAndValidateInputFile(final String inputFilePath) + throws Exception { + LOGGER.info("Parsing input file: " + inputFilePath); + final TestInputConfiguration testInputConfiguration = + InlineConfigParser.parse(inputFilePath); + + if (testInputConfiguration == null) { + throw new IllegalArgumentException("Failed to parse input file: " + inputFilePath); + } + + final List modules = testInputConfiguration.getChildrenModules(); + + if (modules == null || modules.isEmpty()) { + throw new IllegalArgumentException( + "No modules found in the input file: " + inputFilePath + ); + } + + final ModuleInputConfiguration mainModule = modules.get(0); + if (mainModule == null) { + throw new IllegalArgumentException( + "Main module is null in the input file: " + inputFilePath + ); + } + + return mainModule; + } + + /** + * Reads the content of a resource file as a string. + * + * @param resourceName the name of the resource to be read. + * @return the content of the resource file as a string. + * @throws IOException if the resource is not found or if an I/O error occurs. + */ + private static String readResourceAsString(final String resourceName) throws IOException { + try (InputStream inputStream = Thread.currentThread() + .getContextClassLoader() + .getResourceAsStream(resourceName)) { + if (inputStream == null) { + throw new IOException("Resource not found: " + resourceName); + } + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(inputStream, + StandardCharsets.UTF_8))) { + return reader.lines().collect(Collectors.joining("\n")); + } + } } /** diff --git a/extractor/src/test/java/com/example/extractor/MainTest.java b/extractor/src/test/java/com/example/extractor/MainTest.java index 3c3555758..ea9ff440e 100644 --- a/extractor/src/test/java/com/example/extractor/MainTest.java +++ b/extractor/src/test/java/com/example/extractor/MainTest.java @@ -45,10 +45,16 @@ class MainTest { "src/main/resources/config-template-treewalker.xml"; /** - * The constant for non-Treewalker template file. + * The constant for Treewalker template file for input java file conditions. */ - private static final String NON_TREEWALKER_TEMPLATE_FILE = - "src/main/resources/config-template-non-treewalker.xml"; + private static final String TW_TEMPLATE_FILE_INPUT_JAVA = + "config-template-treewalker.xml"; + + /** + * The constant for non-Treewalker template file for input java file conditions. + */ + private static final String NTW_TEMPLATE_FILE_INPUT_JAVA = + "config-template-non-treewalker.xml"; /** * The constant for base path of test resources. @@ -255,7 +261,7 @@ void testGeneratedConfigForNonXmlInputWithTreewalker() throws Exception { + "/whitespace/methodparampad/Config/expected-config.xml"; final String generatedContent = ConfigSerializer.serializeNonXmlConfigToString( - inputFilePath, TREEWALKER_TEMPLATE_FILE); + inputFilePath, TW_TEMPLATE_FILE_INPUT_JAVA); final String expectedContent = loadToString(expectedInputFilePath); assertThat(generatedContent).isEqualTo(expectedContent); } @@ -274,7 +280,7 @@ void testGeneratedConfigForNonXmlInputWithNonTreewalker() throws Exception { + "/whitespace/filetabcharacter/Config/expected-config.xml"; final String generatedContent = ConfigSerializer.serializeNonXmlConfigToString( - inputFilePath, NON_TREEWALKER_TEMPLATE_FILE); + inputFilePath, NTW_TEMPLATE_FILE_INPUT_JAVA); final String expectedContent = loadToString(expectedInputFilePath); assertThat(generatedContent).isEqualTo(expectedContent); } diff --git a/extractor/src/test/java/com/example/extractor/MainsLauncherTest.java b/extractor/src/test/java/com/example/extractor/MainsLauncherTest.java index 718607df2..52a4ec3c3 100644 --- a/extractor/src/test/java/com/example/extractor/MainsLauncherTest.java +++ b/extractor/src/test/java/com/example/extractor/MainsLauncherTest.java @@ -21,9 +21,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -49,6 +51,11 @@ class MainsLauncherTest { private static final String CHECKSTYLE_CHECKS_BASE_PATH = "com/puppycrawl/tools/checkstyle/checks"; + /** + * The constant for default projects file. + */ + private static final String DEFAULT_PROJECTS_FILE = "list-of-projects.properties"; + /** * Tests the main method of CheckstyleExampleExtractor. * This test ensures that the main method runs without throwing any exceptions. @@ -69,31 +76,48 @@ void testMain() { */ @Test void testMainWithInputFile(@TempDir final Path tempDir) throws Exception { - final String inputFilePath = "src/test/resources/" - + CHECKSTYLE_CHECKS_BASE_PATH + final String inputFilePath = "src/test/resources/" + CHECKSTYLE_CHECKS_BASE_PATH + "/whitespace/methodparampad/InputFile/InputMethodParamPadWhitespaceAround.java"; - - final String expectedInputFilePath = "src/test/resources/" - + CHECKSTYLE_CHECKS_BASE_PATH + final String expectedInputFilePath = "src/test/resources/" + CHECKSTYLE_CHECKS_BASE_PATH + "/whitespace/methodparampad/Config/expected-config.xml"; - final String expectedContent = loadToString(expectedInputFilePath); + final String expectedProjectsContent = loadDefaultProjectsList(); - final Path outputFile = tempDir.resolve("output-config.xml"); + final Path outputConfigFile = tempDir.resolve("output-config.xml"); + final Path outputProjectsFile = tempDir.resolve("output-projects.properties"); assertDoesNotThrow(() -> { CheckstyleExampleExtractor.main(new String[]{ CHECKSTYLE_REPO_PATH, "--input-file", inputFilePath, - outputFile.toString(), + outputConfigFile.toString(), + outputProjectsFile.toString(), }); }); - assertTrue(Files.exists(outputFile), "Output file should be created"); - final String generatedContent = Files.readString(outputFile); + assertTrue(Files.exists(outputConfigFile), "Config output file should be created"); + assertTrue(Files.exists(outputProjectsFile), "Projects output file should be created"); + + final String generatedConfigContent = Files.readString(outputConfigFile); + assertThat(generatedConfigContent).isEqualTo(expectedContent); + + final String generatedProjectsContent = Files.readString(outputProjectsFile); + assertFalse(generatedProjectsContent.isEmpty(), "Projects file should not be empty"); + assertThat(generatedProjectsContent).isEqualTo(expectedProjectsContent); + } - assertThat(generatedContent).isEqualTo(expectedContent); + /** + * Loads the default projects list from the resource file. + * + * @return the content of the projects list as a string. + * @throws IOException if an error occurs while reading the resource. + */ + private String loadDefaultProjectsList() throws IOException { + try (InputStream inputStream = Thread.currentThread().getContextClassLoader() + .getResourceAsStream(DEFAULT_PROJECTS_FILE)) { + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } } /**