Skip to content

Commit

Permalink
Issue 214: Support of config bundles with extra configuration files
Browse files Browse the repository at this point in the history
  • Loading branch information
piyush kumar sadangi authored and piyush kumar sadangi committed Dec 5, 2024
1 parent 435cd31 commit 4351c8a
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 48 deletions.
1 change: 1 addition & 0 deletions RegexpHeader/Example2/extra-config-files.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
java.header
2 changes: 2 additions & 0 deletions extractor/config/checkstyle/suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@

<!-- Suppress UncommentedMain check for the specific class -->
<suppress checks="UncommentedMain" files="com/example/extractor/CheckstyleExampleExtractor.java"/>
<suppress checks="MethodCount" files="com/example/extractor/CheckstyleExampleExtractor.java"/>

</suppressions>
3 changes: 3 additions & 0 deletions extractor/config/pmd/pmd-ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
<!-- AvoidCatchingGenericException is excluded to allow for broad exception handling
in certain scenarios where specific exceptions are not easily anticipated. -->
<exclude name="AvoidCatchingGenericException"/>
<!-- We have excluded due to the nature of the application, as a temporary fix.
We will come back to refactor this better. -->
<exclude name="CyclomaticComplexity" />
</rule>

<rule ref="category/java/documentation.xml">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,35 +82,29 @@ public final class CheckstyleExampleExtractor {
/** The subfolder name for all-in-one examples. */
private static final String ALL_IN_ONE_SUBFOLDER = "all-examples-in-one";

/**
* Number of expected arguments when processing a single input file.
*/
private static final int SINGLE_INPUT_FILE_ARG_COUNT = 5;
/** The buffer size for reading and writing files. */
private static final int BUFFER_SIZE = 1024;

/**
* Index of the "--input-file" flag in the argument array.
*/
private static final int INPUT_FILE_FLAG_INDEX = 1;
/** Number of expected arguments when processing a configuration file. */
private static final int CONFIG_FILE_ARG_COUNT = 5;

/**
* Index of the input file path in the argument array.
*/
/** Index of the "--config-file" flag in the argument array. */
private static final int CONFIG_FILE_FLAG_INDEX = 1;

/** Index of the configuration file path in the argument array. */
private static final int CONFIG_FILE_PATH_INDEX = 2;

/** Index of the input file path in the argument array. */
private static final int INPUT_FILE_PATH_INDEX = 2;

/**
* Index of the output file path in the argument array.
*/
private static final int OUTPUT_FILE_PATH_INDEX = 3;
/** Index of the output file path in the argument array. */
private static final int CONFIG_OUTPUT_PATH_INDEX = 3;

/**
* Index of the output file path in the argument array.
*/
private static final int PROJECT_OUTPUT_PATH_INDEX = 4;
/** Index of the projects output path in the argument array. */
private static final int CONFIG_PROJECTS_PATH_INDEX = 4;

/**
* The buffer size for reading and writing files.
*/
private static final int BUFFER_SIZE = 1024;
/** The configuration path variable placeholder. */
private static final String CONFIG_PATH_VARIABLE = "${config.path}";

/**
* Private constructor to prevent instantiation of this utility class.
Expand All @@ -124,31 +118,23 @@ private CheckstyleExampleExtractor() {
*
* @param args Command line arguments
* @throws Exception If an error occurs during processing
* @throws IllegalArgumentException if the argument is invalid.
* @throws IllegalArgumentException if the argument is invalid
*/
public static void main(final String[] args) throws Exception {
if (args.length < 1) {
throw new IllegalArgumentException(
"Usage: <checkstyle repo path> [--input-config <config content> "
+ "<output file path>]"
"Usage: <checkstyle repo path> [--input-file <input file path> "
+ "<config output path> <projects output path>] "
+ "or [--config-file <config file path> "
+ "<config output path> <projects output path>]"
);
}

if (args.length == SINGLE_INPUT_FILE_ARG_COUNT
&& "--input-file".equals(args[INPUT_FILE_FLAG_INDEX])) {
// New functionality: process single input file
final String inputFilePath = args[INPUT_FILE_PATH_INDEX];
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);
if (args.length >= CONFIG_FILE_ARG_COUNT) {
processCommandLineOption(args);
}
else {
// Functionality: process all examples
// Original functionality: process all examples
final String checkstyleRepoPath = args[0];
final List<Path> allExampleDirs = findAllExampleDirs(checkstyleRepoPath);
final Map<String, List<Path>> moduleExamples = processExampleDirs(allExampleDirs);
Expand All @@ -162,6 +148,232 @@ public static void main(final String[] args) throws Exception {
}
}

/**
* Process command line options.
*
* @param args Command line arguments
* @throws Exception If an error occurs during processing
* @throws IllegalArgumentException if an unknown option is provided
*/
private static void processCommandLineOption(final String... args) throws Exception {
final String option = args[CONFIG_FILE_FLAG_INDEX];
processOption(option, args);
}

/**
* Process the given option with arguments.
*
* @param option The option to process
* @param args Command line arguments
* @throws IllegalArgumentException If an unknown option
* @throws Exception If an error occurs during processing
*/
private static void processOption(final String option, final String... args) throws Exception {
switch (option) {
case "--input-file":
processInputFileOption(args);
break;
case "--config-file":
processConfigFileOption(args);
break;
default:
throw new IllegalArgumentException("Unknown option: " + option);
}
}

/**
* Process input file option.
*
* @param args Command line arguments
* @throws Exception If an error occurs during processing
*/
private static void processInputFileOption(final String... args) throws Exception {
final Path inputFile = Paths.get(args[INPUT_FILE_PATH_INDEX]);
final Path configOutputFile = Paths.get(args[CONFIG_OUTPUT_PATH_INDEX]);
final Path projectsOutputFile = Paths.get(args[CONFIG_PROJECTS_PATH_INDEX]);

processInputFile(inputFile, configOutputFile);
outputDefaultProjectsList(projectsOutputFile.toString());
}

/**
* Process config file option.
*
* @param args Command line arguments
* @throws Exception If an error occurs during processing
*/
private static void processConfigFileOption(final String... args) throws Exception {
final Path configFile = Paths.get(args[CONFIG_FILE_PATH_INDEX]);
final Path configOutputFile = Paths.get(args[CONFIG_OUTPUT_PATH_INDEX]);
final Path projectsOutputFile = Paths.get(args[CONFIG_PROJECTS_PATH_INDEX]);

processConfigFile(configFile, configOutputFile);
outputDefaultProjectsList(projectsOutputFile.toString());
}

/**
* Processes external files referenced in the configuration content.
*
* @param configContent The original configuration content.
* @param configFile The path to the configuration file.
* @param outputFile The path to the output file.
* @return The updated configuration content with paths replaced.
* @throws IOException If an I/O error occurs during file operations.
*/
private static String processExternalFilesInConfig(
final String configContent,
final Path configFile,
final Path outputFile) throws IOException {
final Pattern pattern = Pattern.compile("<property name=\"([^\"]+)\" value=\"([^\"]+)\"/>");
final Matcher matcher = pattern.matcher(configContent);
String processedContent = configContent;
while (matcher.find()) {
final String propertyValue = matcher.group(2);
if (isExternalProperty(propertyValue)) {
processedContent = processProperty(propertyValue, processedContent, configFile,
outputFile);
}
}
return processedContent;
}

/**
* Checks if the property value references an external file.
*
* @param propertyValue The value of the property to check.
* @return True if the property references an external file; false otherwise.
*/
private static boolean isExternalProperty(final String propertyValue) {
return propertyValue.contains("config/java.header")
|| propertyValue.contains("${execution.path}");
}

/**
* Processes a single property by replacing paths and copying external files.
*
* @param propertyValue The original property value.
* @param content The current configuration content.
* @param configFile The path to the configuration file.
* @param outputFile The path to the output file.
* @return The updated configuration content.
* @throws IOException If an I/O error occurs.
*/
private static String processProperty(
final String propertyValue,
final String content,
final Path configFile,
final Path outputFile) throws IOException {
// Replace the path with ${config.path}
final String newPropertyValue = propertyValue
.replace("${execution.path}", CONFIG_PATH_VARIABLE)
.replace("config/java.header", CONFIG_PATH_VARIABLE + "/java.header");

// Update the content
final String updatedContent = content.replace(propertyValue, newPropertyValue);

// Copy the external file to the output directory
copyExternalFile(configFile, outputFile);

return updatedContent;
}

/**
* Copies the external file referenced in the configuration to the output directory.
*
* @param configFile The path to the configuration file.
* @param outputFile The path to the output file.
* @throws IOException If an I/O error occurs during file operations.
*/
private static void copyExternalFile(final Path configFile, final Path outputFile)
throws IOException {
final Path configParent = getParentPathOrWarn(configFile, "Config file");
final Path outputParent = getParentPathOrWarn(outputFile, "Output file");
processParentPaths(configParent, outputParent);
}

/**
* Process parent paths and copy files if valid.
*
* @param configParent The parent path of the config file
* @param outputParent The parent path of the output file
* @throws IOException If an I/O error occurs
*/
private static void processParentPaths(final Path configParent, final Path outputParent)
throws IOException {
if (configParent == null || outputParent == null) {
handleNullParentPath("Parent directory");
}
else {
final Path sourceHeaderFile = configParent.resolve("java.header");
final Path targetHeaderFile = outputParent.resolve("config/java.header");
copyFileIfExists(sourceHeaderFile, targetHeaderFile);
}
}

/**
* Copies a file if it exists.
*
* @param sourceFile The source file path.
* @param targetFile The target file path.
* @throws IOException If an I/O error occurs during file operations.
* @throws IllegalArgumentException if target file has no parent directory
*/
private static void copyFileIfExists(final Path sourceFile, final Path targetFile)
throws IOException {
if (Files.exists(sourceFile)) {
final Path targetParent = targetFile.getParent();
if (targetParent == null) {
throw new IllegalArgumentException("Target file must have a parent directory: "
+ targetFile);
}
Files.createDirectories(targetParent);
Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
}
else {
LOGGER.warning("External file not found: " + sourceFile);
}
}

/**
* Handles null parent path case by logging a warning.
*
* @param fileType The type of file (for logging purposes)
*/
private static void handleNullParentPath(final String fileType) {
LOGGER.warning(fileType + " has no parent directory");
}

/**
* Processes a configuration file and generates an output file.
*
* @param configFile The path to the configuration file
* @param outputFile The path to the output file
* @throws Exception If an error occurs during processing
* @throws IOException If an error occurs during processing
*/
public static void processConfigFile(
final Path configFile,
final Path outputFile)
throws Exception {
// Check if the config file exists
if (!Files.exists(configFile)) {
LOGGER.severe("Config file does not exist: " + configFile);
throw new IOException("Config file does not exist: " + configFile);
}

// Read the config file content
final String configContent = Files.readString(configFile, StandardCharsets.UTF_8);

// Process external files in the config content
final String updatedContent = processExternalFilesInConfig(
configContent, configFile, outputFile);

// Write the updated content to the output file
Files.writeString(outputFile, updatedContent, StandardCharsets.UTF_8);

LOGGER.info("Generated configuration at " + outputFile);
}

/**
* Writes the default projects list to the specified file.
*
Expand Down Expand Up @@ -794,4 +1006,19 @@ private static int extractExampleNumber(final String filename) {

return exampleNumber;
}

/**
* Retrieves the parent path of a given path, logging a warning if it is null.
*
* @param path The path to get the parent of
* @param description A description used in the warning message
* @return The parent path, or null if it does not exist
*/
private static Path getParentPathOrWarn(final Path path, final String description) {
final Path parent = path.getParent();
if (parent == null) {
LOGGER.warning(description + " has no parent directory: " + path);
}
return parent;
}
}
Loading

0 comments on commit 4351c8a

Please sign in to comment.