Skip to content

Commit 91da9fd

Browse files
committed
refactor: Remove JShell for custom resources post-processing
1 parent 508e53a commit 91da9fd

File tree

17 files changed

+827
-202
lines changed

17 files changed

+827
-202
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Changed
1313

1414
- Correction of various typos in rules documentations
15+
- 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.
1516

1617
### Deleted
1718

maven-build/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This directory contains processing code used during the Maven build.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.greencodeinitiative.mavenbuild.ruleexporter;
2+
3+
import org.greencodeinitiative.mavenbuild.ruleexporter.infra.PrepareResources;
4+
5+
import java.nio.file.Path;
6+
import java.util.List;
7+
import java.util.Optional;
8+
9+
import static java.util.Collections.emptyList;
10+
import static java.util.Optional.empty;
11+
import static java.util.Optional.ofNullable;
12+
13+
public class Main implements Runnable {
14+
private final List<String> args;
15+
16+
public Main(String[] args) {
17+
this.args = ofNullable(args).map(List::of).orElse(emptyList());
18+
}
19+
20+
public static void main(String... args) {
21+
new Main(args).run();
22+
}
23+
24+
@Override
25+
public void run() {
26+
Path sourceDir = argAsPath(0, "sourceDir");
27+
Path targetDir = argAsPath(1, "targetDir");
28+
new PrepareResources(
29+
sourceDir,
30+
targetDir
31+
).run();
32+
}
33+
34+
private Optional<String> optionalArg(int index) {
35+
if (args.size() <= index) {
36+
return empty();
37+
}
38+
return Optional.of(args.get(index));
39+
}
40+
41+
private String arg(int index, String description) {
42+
if (args.size() <= index) {
43+
throw new IllegalArgumentException("argument " + (index + 1) + " is required: " + description);
44+
}
45+
return optionalArg(index).orElseThrow(() -> new IllegalArgumentException("argument " + (index + 1) + " is required: " + description));
46+
}
47+
48+
private Path argAsPath(int index, String description) {
49+
return Path.of(arg(index, description));
50+
}
51+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.greencodeinitiative.mavenbuild.ruleexporter.domain;
2+
3+
import java.nio.file.Files;
4+
import java.nio.file.Path;
5+
import java.util.Optional;
6+
import java.util.regex.Matcher;
7+
import java.util.regex.Pattern;
8+
9+
import static java.util.Optional.empty;
10+
import static java.util.Optional.of;
11+
12+
public class Rule {
13+
/**
14+
* Resources to include
15+
*/
16+
private static final Pattern TARGET_RESOURCES = Pattern.compile("^.{1,1000}/(?<ruleKey>GCI\\d{1,50})/(?<language>[^/]{1,50})/GCI\\d{1,50}\\.html$");
17+
18+
public static Optional<Rule> createFromHtmlDescription(Path htmlDescription) {
19+
final Matcher matcher = TARGET_RESOURCES.matcher(htmlDescription.toString().replace('\\', '/'));
20+
if (!matcher.find()) {
21+
return empty();
22+
}
23+
final String ruleKey = matcher.group("ruleKey");
24+
final Path metadata = htmlDescription.getParent().getParent().resolve(ruleKey + ".json");
25+
final Path specificMetadata = htmlDescription.getParent().resolve(ruleKey + ".json");
26+
27+
if (!Files.isRegularFile(htmlDescription) || !Files.isRegularFile(metadata)) {
28+
return empty();
29+
}
30+
31+
return of(new Rule(
32+
matcher.group("language"),
33+
htmlDescription,
34+
metadata,
35+
specificMetadata
36+
));
37+
}
38+
39+
private final String language;
40+
private final Path htmlDescription;
41+
private final Path metadata;
42+
private final Path specificMetadata;
43+
44+
public Rule(String language, Path htmlDescription, Path metadata, Path specificMetadata) {
45+
this.language = language;
46+
this.htmlDescription = htmlDescription;
47+
this.metadata = metadata;
48+
this.specificMetadata = specificMetadata;
49+
}
50+
51+
public Path getHtmlDescriptionTargetPath(Path targetDir) {
52+
return targetDir.resolve(language).resolve(htmlDescription.getFileName());
53+
}
54+
55+
public Path getMetadataTargetPath(Path targetDir) {
56+
return targetDir.resolve(language).resolve(metadata.getFileName());
57+
}
58+
59+
public String language() {
60+
return language;
61+
}
62+
63+
public Path htmlDescription() {
64+
return htmlDescription;
65+
}
66+
67+
public Path metadata() {
68+
return metadata;
69+
}
70+
71+
public Path specificMetadata() {
72+
return specificMetadata;
73+
}
74+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package org.greencodeinitiative.mavenbuild.ruleexporter.infra;
2+
3+
import jakarta.json.Json;
4+
import jakarta.json.JsonMergePatch;
5+
import jakarta.json.JsonObject;
6+
import jakarta.json.JsonReader;
7+
import jakarta.json.JsonValue;
8+
import jakarta.json.JsonWriter;
9+
import org.greencodeinitiative.mavenbuild.ruleexporter.domain.Rule;
10+
11+
import java.io.IOException;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.nio.file.StandardCopyOption;
15+
import java.util.List;
16+
import java.util.Optional;
17+
import java.util.stream.Collectors;
18+
import java.util.stream.Stream;
19+
20+
import static java.lang.System.Logger.Level.DEBUG;
21+
22+
public class PrepareResources implements Runnable {
23+
private static final System.Logger LOGGER = System.getLogger("PrepareResources");
24+
25+
private final Path sourceDir;
26+
private final Path targetDir;
27+
28+
public PrepareResources(Path sourceDir, Path targetDir) {
29+
this.sourceDir = sourceDir;
30+
this.targetDir = targetDir;
31+
}
32+
33+
@Override
34+
public void run() {
35+
getResourcesToCopy().forEach(rule -> {
36+
mergeOrCopyJsonMetadata(rule.metadata(), rule.specificMetadata(), rule.getMetadataTargetPath(targetDir));
37+
copyFile(rule.htmlDescription(), rule.getHtmlDescriptionTargetPath(targetDir));
38+
});
39+
}
40+
41+
private List<Rule> getResourcesToCopy() {
42+
try (Stream<Path> stream = Files.walk(sourceDir)) {
43+
return stream
44+
.filter(Files::isRegularFile)
45+
.map(Rule::createFromHtmlDescription)
46+
.filter(Optional::isPresent)
47+
.map(Optional::get)
48+
.collect(Collectors.toList());
49+
} catch (IOException e) {
50+
throw new IllegalStateException("Unable to read file", e);
51+
}
52+
}
53+
54+
void mergeOrCopyJsonMetadata(Path source, Path merge, Path target) {
55+
try {
56+
Files.createDirectories(target.getParent());
57+
} catch (IOException e) {
58+
throw new ProcessException(e);
59+
}
60+
if (Files.isRegularFile(merge)) {
61+
mergeJsonFile(source, merge, target);
62+
} else {
63+
copyFile(source, target);
64+
}
65+
}
66+
67+
void mergeJsonFile(Path source, Path merge, Path target) {
68+
LOGGER.log(DEBUG, "Merge: {0} and {1} -> {2}", source, merge, target);
69+
70+
try (
71+
JsonReader sourceJsonReader = Json.createReader(Files.newBufferedReader(source));
72+
JsonReader mergeJsonReader = Json.createReader(Files.newBufferedReader(merge));
73+
JsonWriter resultJsonWriter = Json.createWriter(Files.newBufferedWriter(target));
74+
) {
75+
Files.createDirectories(target.getParent());
76+
77+
JsonObject sourceJson = sourceJsonReader.readObject();
78+
JsonObject mergeJson = mergeJsonReader.readObject();
79+
80+
JsonMergePatch mergePatch = Json.createMergePatch(mergeJson);
81+
JsonValue result = mergePatch.apply(sourceJson);
82+
83+
resultJsonWriter.write(result);
84+
} catch (IOException e) {
85+
throw new ProcessException("cannot process source " + source, e);
86+
}
87+
}
88+
89+
void copyFile(Path source, Path target) {
90+
try {
91+
LOGGER.log(DEBUG, "Copy: {0} -> {1}", source, target);
92+
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
93+
} catch (IOException e) {
94+
throw new ProcessException("unable to copy '" + source + "' to '" + target + "'", e);
95+
}
96+
}
97+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.greencodeinitiative.mavenbuild.ruleexporter.infra;
2+
3+
public class ProcessException extends RuntimeException {
4+
public ProcessException(Throwable cause) {
5+
super(cause);
6+
}
7+
8+
public ProcessException(String message, Throwable cause) {
9+
super(message, cause);
10+
}
11+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package org.greencodeinitiative.mavenbuild.ruleexporter.domain;
2+
3+
import org.assertj.core.api.AbstractObjectAssert;
4+
import org.junit.jupiter.api.DisplayName;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.io.TempDir;
7+
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
import java.util.Optional;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
class RuleTest {
15+
private final Path htmlDescription = Path.of("some/path/file.html");
16+
private final Path metadata = Path.of("some/path/metadata.json");
17+
private final Path specificMetadata = Path.of("some/path/specificMetadata.json");
18+
private final Path targetDir = Path.of("target");
19+
20+
@Test
21+
@DisplayName("Should return an empty Optional when the path does not match the expected pattern")
22+
void createFromHtmlDescriptionShouldReturnEmptyWhenPathDoesNotMatchPattern() {
23+
// Given
24+
Path invalidPath = Path.of("invalid/path/to/file.html");
25+
26+
// When
27+
Optional<Rule> result = Rule.createFromHtmlDescription(invalidPath);
28+
29+
// Then
30+
assertThat(result).isEmpty();
31+
}
32+
33+
@Test
34+
@DisplayName("Should return correct values for all getters")
35+
void gettersShouldReturnCorrectValues() {
36+
// Given
37+
Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata);
38+
39+
// When & Then
40+
assertThat(rule.language()).isEqualTo("java");
41+
assertThat(rule.htmlDescription()).isEqualTo(htmlDescription);
42+
assertThat(rule.metadata()).isEqualTo(metadata);
43+
assertThat(rule.specificMetadata()).isEqualTo(specificMetadata);
44+
}
45+
46+
@Test
47+
@DisplayName("Should return the correct target path for the HTML description")
48+
void getHtmlDescriptionTargetPathShouldReturnCorrectPath() {
49+
// Given
50+
Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata);
51+
Path expectedPath = targetDir.resolve("java").resolve("file.html");
52+
53+
// When
54+
Path actualPath = rule.getHtmlDescriptionTargetPath(targetDir);
55+
56+
// Then
57+
assertThat(actualPath).isEqualTo(expectedPath);
58+
}
59+
60+
@Test
61+
@DisplayName("Should return the correct target path for the metadata file")
62+
void getMetadataTargetPathShouldReturnCorrectPath() {
63+
// Given
64+
Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata);
65+
Path expectedPath = targetDir.resolve("java").resolve("metadata.json");
66+
67+
// When
68+
Path actualPath = rule.getMetadataTargetPath(targetDir);
69+
70+
// Then
71+
assertThat(actualPath).isEqualTo(expectedPath);
72+
}
73+
74+
@Test
75+
@DisplayName("Should return empty Optional if htmlDescription file does not exist")
76+
void createFromHtmlDescriptionShouldReturnEmptyIfHtmlFileMissing() {
77+
// Given
78+
Path html = Path.of("/GCI123/java/GCI123.html");
79+
80+
// When
81+
Optional<Rule> result = Rule.createFromHtmlDescription(html);
82+
83+
// Then
84+
assertThat(result).isEmpty();
85+
}
86+
87+
@Test
88+
@DisplayName("Should return empty Optional if metadata file does not exist")
89+
void createFromHtmlDescriptionShouldReturnEmptyIfMetadataFileMissing(@TempDir Path tempDir) throws Exception {
90+
// Given
91+
Path htmlPath = tempDir.resolve("GCI123/java/GCI123.html");
92+
Files.createDirectories(htmlPath.getParent());
93+
Files.createFile(htmlPath);
94+
95+
// When
96+
Optional<Rule> result = Rule.createFromHtmlDescription(htmlPath);
97+
98+
// Then
99+
assertThat(result).isEmpty();
100+
}
101+
102+
@Test
103+
@DisplayName("Should create Rule when files and pattern are valid")
104+
void createFromHtmlDescriptionShouldReturnRuleWhenFilesExist(@TempDir Path tempDir) throws Exception {
105+
// Given
106+
Path ruleDir = Files.createDirectories(tempDir.resolve("GCI123"));
107+
Path languageRuleDir = Files.createDirectories(ruleDir.resolve("java"));
108+
109+
Path htmlDescriptionPath = Files.createFile(languageRuleDir.resolve("GCI123.html"));
110+
Path metadataPath = Files.createFile(ruleDir.resolve("GCI123.json"));
111+
112+
// When
113+
Optional<Rule> result = Rule.createFromHtmlDescription(htmlDescriptionPath);
114+
115+
// Then
116+
AbstractObjectAssert<?, Rule> assertThatRule = assertThat(result).isPresent().get();
117+
assertThatRule.extracting(Rule::language).isEqualTo("java");
118+
assertThatRule.extracting(Rule::htmlDescription).isEqualTo(htmlDescriptionPath);
119+
assertThatRule.extracting(Rule::metadata).isEqualTo(metadataPath);
120+
assertThatRule.extracting(Rule::specificMetadata).isEqualTo(languageRuleDir.resolve("GCI123.json"));
121+
}
122+
}

0 commit comments

Comments
 (0)