Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Correction of various typos in rules documentations
- Changes the file post-processing process to use Java code instead of a JShell script. This reduces build times and also allows for testing the process.

### Deleted

Expand Down
1 change: 1 addition & 0 deletions maven-build/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This directory contains processing code used during the Maven build.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.greencodeinitiative.mavenbuild.ruleexporter;

import org.greencodeinitiative.mavenbuild.ruleexporter.infra.PrepareResources;

import java.nio.file.Path;
import java.util.List;
import java.util.Optional;

import static java.util.Collections.emptyList;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;

public class Main implements Runnable {
private final List<String> args;

public Main(String[] args) {
this.args = ofNullable(args).map(List::of).orElse(emptyList());
}

public static void main(String... args) {
new Main(args).run();
}

@Override
public void run() {
Path sourceDir = argAsPath(0, "sourceDir");
Path targetDir = argAsPath(1, "targetDir");
new PrepareResources(
sourceDir,
targetDir
).run();
}

private Optional<String> optionalArg(int index) {
if (args.size() <= index) {
return empty();
}
return Optional.of(args.get(index));
}

private String arg(int index, String description) {
if (args.size() <= index) {
throw new IllegalArgumentException("argument " + (index + 1) + " is required: " + description);
}
return optionalArg(index).orElseThrow(() -> new IllegalArgumentException("argument " + (index + 1) + " is required: " + description));
}

private Path argAsPath(int index, String description) {
return Path.of(arg(index, description));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.greencodeinitiative.mavenbuild.ruleexporter.domain;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.Optional.empty;
import static java.util.Optional.of;

public class Rule {
/**
* Resources to include
*/
private static final Pattern TARGET_RESOURCES = Pattern.compile("^.{1,1000}/(?<ruleKey>GCI\\d{1,50})/(?<language>[^/]{1,50})/GCI\\d{1,50}\\.html$");

public static Optional<Rule> createFromHtmlDescription(Path htmlDescription) {
final Matcher matcher = TARGET_RESOURCES.matcher(htmlDescription.toString().replace('\\', '/'));
if (!matcher.find()) {
return empty();
}
final String ruleKey = matcher.group("ruleKey");
final Path metadata = htmlDescription.getParent().getParent().resolve(ruleKey + ".json");
final Path specificMetadata = htmlDescription.getParent().resolve(ruleKey + ".json");

if (!Files.isRegularFile(htmlDescription) || !Files.isRegularFile(metadata)) {
return empty();
}

return of(new Rule(
matcher.group("language"),
htmlDescription,
metadata,
specificMetadata
));
}

private final String language;
private final Path htmlDescription;
private final Path metadata;
private final Path specificMetadata;

public Rule(String language, Path htmlDescription, Path metadata, Path specificMetadata) {
this.language = language;
this.htmlDescription = htmlDescription;
this.metadata = metadata;
this.specificMetadata = specificMetadata;
}

public Path getHtmlDescriptionTargetPath(Path targetDir) {
return targetDir.resolve(language).resolve(htmlDescription.getFileName());
}

public Path getMetadataTargetPath(Path targetDir) {
return targetDir.resolve(language).resolve(metadata.getFileName());
}

public String language() {
return language;
}

public Path htmlDescription() {
return htmlDescription;
}

public Path metadata() {
return metadata;
}

public Path specificMetadata() {
return specificMetadata;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.greencodeinitiative.mavenbuild.ruleexporter.infra;

import jakarta.json.Json;
import jakarta.json.JsonMergePatch;
import jakarta.json.JsonObject;
import jakarta.json.JsonReader;
import jakarta.json.JsonValue;
import jakarta.json.JsonWriter;
import org.greencodeinitiative.mavenbuild.ruleexporter.domain.Rule;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.System.Logger.Level.DEBUG;

public class PrepareResources implements Runnable {
private static final System.Logger LOGGER = System.getLogger("PrepareResources");

private final Path sourceDir;
private final Path targetDir;

public PrepareResources(Path sourceDir, Path targetDir) {
this.sourceDir = sourceDir;
this.targetDir = targetDir;
}

@Override
public void run() {
getResourcesToCopy().forEach(rule -> {
mergeOrCopyJsonMetadata(rule.metadata(), rule.specificMetadata(), rule.getMetadataTargetPath(targetDir));
copyFile(rule.htmlDescription(), rule.getHtmlDescriptionTargetPath(targetDir));
});
}

private List<Rule> getResourcesToCopy() {
try (Stream<Path> stream = Files.walk(sourceDir)) {
return stream
.filter(Files::isRegularFile)
.map(Rule::createFromHtmlDescription)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
} catch (IOException e) {
throw new IllegalStateException("Unable to read file", e);
}
}

void mergeOrCopyJsonMetadata(Path source, Path merge, Path target) {
try {
Files.createDirectories(target.getParent());
} catch (IOException e) {
throw new ProcessException(e);
}
if (Files.isRegularFile(merge)) {
mergeJsonFile(source, merge, target);
} else {
copyFile(source, target);
}
}

void mergeJsonFile(Path source, Path merge, Path target) {
LOGGER.log(DEBUG, "Merge: {0} and {1} -> {2}", source, merge, target);

try (
JsonReader sourceJsonReader = Json.createReader(Files.newBufferedReader(source));
JsonReader mergeJsonReader = Json.createReader(Files.newBufferedReader(merge));
JsonWriter resultJsonWriter = Json.createWriter(Files.newBufferedWriter(target));
) {
Files.createDirectories(target.getParent());

JsonObject sourceJson = sourceJsonReader.readObject();
JsonObject mergeJson = mergeJsonReader.readObject();

JsonMergePatch mergePatch = Json.createMergePatch(mergeJson);
JsonValue result = mergePatch.apply(sourceJson);

resultJsonWriter.write(result);
} catch (IOException e) {
throw new ProcessException("cannot process source " + source, e);
}
}

void copyFile(Path source, Path target) {
try {
LOGGER.log(DEBUG, "Copy: {0} -> {1}", source, target);
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new ProcessException("unable to copy '" + source + "' to '" + target + "'", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.greencodeinitiative.mavenbuild.ruleexporter.infra;

public class ProcessException extends RuntimeException {
public ProcessException(Throwable cause) {
super(cause);
}

public ProcessException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package org.greencodeinitiative.mavenbuild.ruleexporter.domain;

import org.assertj.core.api.AbstractObjectAssert;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;

class RuleTest {
private final Path htmlDescription = Path.of("some/path/file.html");
private final Path metadata = Path.of("some/path/metadata.json");
private final Path specificMetadata = Path.of("some/path/specificMetadata.json");
private final Path targetDir = Path.of("target");

@Test
@DisplayName("Should return an empty Optional when the path does not match the expected pattern")
void createFromHtmlDescriptionShouldReturnEmptyWhenPathDoesNotMatchPattern() {
// Given
Path invalidPath = Path.of("invalid/path/to/file.html");

// When
Optional<Rule> result = Rule.createFromHtmlDescription(invalidPath);

// Then
assertThat(result).isEmpty();
}

@Test
@DisplayName("Should return correct values for all getters")
void gettersShouldReturnCorrectValues() {
// Given
Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata);

// When & Then
assertThat(rule.language()).isEqualTo("java");
assertThat(rule.htmlDescription()).isEqualTo(htmlDescription);
assertThat(rule.metadata()).isEqualTo(metadata);
assertThat(rule.specificMetadata()).isEqualTo(specificMetadata);
}

@Test
@DisplayName("Should return the correct target path for the HTML description")
void getHtmlDescriptionTargetPathShouldReturnCorrectPath() {
// Given
Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata);
Path expectedPath = targetDir.resolve("java").resolve("file.html");

// When
Path actualPath = rule.getHtmlDescriptionTargetPath(targetDir);

// Then
assertThat(actualPath).isEqualTo(expectedPath);
}

@Test
@DisplayName("Should return the correct target path for the metadata file")
void getMetadataTargetPathShouldReturnCorrectPath() {
// Given
Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata);
Path expectedPath = targetDir.resolve("java").resolve("metadata.json");

// When
Path actualPath = rule.getMetadataTargetPath(targetDir);

// Then
assertThat(actualPath).isEqualTo(expectedPath);
}

@Test
@DisplayName("Should return empty Optional if htmlDescription file does not exist")
void createFromHtmlDescriptionShouldReturnEmptyIfHtmlFileMissing() {
// Given
Path html = Path.of("/GCI123/java/GCI123.html");

// When
Optional<Rule> result = Rule.createFromHtmlDescription(html);

// Then
assertThat(result).isEmpty();
}

@Test
@DisplayName("Should return empty Optional if metadata file does not exist")
void createFromHtmlDescriptionShouldReturnEmptyIfMetadataFileMissing(@TempDir Path tempDir) throws Exception {
// Given
Path htmlPath = tempDir.resolve("GCI123/java/GCI123.html");
Files.createDirectories(htmlPath.getParent());
Files.createFile(htmlPath);

// When
Optional<Rule> result = Rule.createFromHtmlDescription(htmlPath);

// Then
assertThat(result).isEmpty();
}

@Test
@DisplayName("Should create Rule when files and pattern are valid")
void createFromHtmlDescriptionShouldReturnRuleWhenFilesExist(@TempDir Path tempDir) throws Exception {
// Given
Path ruleDir = Files.createDirectories(tempDir.resolve("GCI123"));
Path languageRuleDir = Files.createDirectories(ruleDir.resolve("java"));

Path htmlDescriptionPath = Files.createFile(languageRuleDir.resolve("GCI123.html"));
Path metadataPath = Files.createFile(ruleDir.resolve("GCI123.json"));

// When
Optional<Rule> result = Rule.createFromHtmlDescription(htmlDescriptionPath);

// Then
AbstractObjectAssert<?, Rule> assertThatRule = assertThat(result).isPresent().get();
assertThatRule.extracting(Rule::language).isEqualTo("java");
assertThatRule.extracting(Rule::htmlDescription).isEqualTo(htmlDescriptionPath);
assertThatRule.extracting(Rule::metadata).isEqualTo(metadataPath);
assertThatRule.extracting(Rule::specificMetadata).isEqualTo(languageRuleDir.resolve("GCI123.json"));
}
}
Loading