LintingMojo.java
package org.thewonderlemming.c4plantuml.mojo.linting;
import java.io.IOException;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.thewonderlemming.c4plantuml.linter.Linter;
import org.thewonderlemming.c4plantuml.linter.rules.AbstractLintingRule;
import org.thewonderlemming.c4plantuml.mojo.AbstractParentMojo;
import org.thewonderlemming.c4plantuml.mojo.MojoReporter;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.builtin.BuiltInLintingRulesFactory;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.custom.AbstractCustomLintingRule;
import org.thewonderlemming.c4plantuml.mojo.linting.rules.custom.AbstractCustomLintingRuleFactory;
/**
* A MOJO that parses the C4 PlantUML files that are contained in the output directory and applies a set of linting
* rules to find violations. Breaks the build on violations if said so.
*
* @author thewonderlemming
*
*/
@Mojo(name = "lint", defaultPhase = LifecyclePhase.PROCESS_CLASSES)
public class LintingMojo extends AbstractParentMojo {
private Set<AbstractLintingRule> builtInRules;
@Parameter(property = "rules", alias = "rules")
private BuiltInRules builtInRulesConfiguration;
private Set<AbstractLintingRule> customRules;
@Parameter(property = "customRules", alias = "customRules")
private Properties customRulesConfiguration;
@Parameter(property = "failOnLintErrors", defaultValue = "true", alias = "failOnLintErrors")
private boolean failOnLintErrors;
private MojoReporter reporter;
private static boolean isCustomRule(final Class<? extends AbstractLintingRule> rule) {
return AbstractCustomLintingRule.class.isAssignableFrom(rule);
}
private static boolean isNotCustomRule(final Class<? extends AbstractLintingRule> rule) {
return !isCustomRule(rule);
}
/**
* Parses the C4 PlantUML files to find linting violations, and breaks the build if the {@code failOnLintErrors} is
* set to {@code true}.
* <p>
* {@inheritDoc}
*/
@Override
protected void doExecute() throws MojoExecutionException, MojoFailureException {
validateBuiltInRulesConfiguration();
validateCustomRulesConfiguration();
setReporter();
retrieveAndSetRules();
lintOutputDirectory();
reportsAndBreakBuildIfNecessary();
}
private Optional<AbstractLintingRule> buildAndConfigureBuiltInRule(
final Class<? extends AbstractLintingRule> ruleType) {
final Optional<String> ruleName = BuiltInLintingRulesFactory.getRuleNameForType(ruleType);
assert (ruleName.isPresent()) : "Expected rule " + ruleType.getName() + " to be found in "
+ BuiltInLintingRulesFactory.class.getName();
return ruleName.isPresent()
? BuiltInLintingRulesFactory.createInstanceForName(ruleName.get(), this.builtInRulesConfiguration)
: Optional.empty();
}
private Optional<? extends AbstractCustomLintingRule> buildAndConfigureCustomRules(
final Class<? extends AbstractCustomLintingRuleFactory<? extends AbstractCustomLintingRule>> factoryClass) {
try {
return factoryClass.newInstance().createCustomRule(customRulesConfiguration);
} catch (InstantiationException | IllegalAccessException e) {
final String errMsg = String
.format("Error while trying to build custom rule from factory %s: %s",
factoryClass.getName(),
e.getMessage());
getLog().error(errMsg);
return Optional.empty();
}
}
private void lintOutputDirectory() throws MojoFailureException {
assert (getOutputDirectory() != null) : "Expected outputDirectory to be set";
assert (this.reporter != null) : "Expected reporter to be set";
assert ((this.builtInRules != null && !this.builtInRules.isEmpty())
|| (this.customRules != null && !this.customRules.isEmpty())) : "Expected rules to be set";
try {
final Linter linter = Linter
.builder()
.newLinter()
.withReporter(this.reporter)
.addLintingRules(this.builtInRules)
.addLintingRules(this.customRules)
.build();
processOutputDirectoryFiles(path -> linter.lint(path, getCurrentCharset()));
} catch (final IOException e) {
final String errMsg = "Cannot process output directory: \"" + getOutputDirectory() + "\" for linting";
throw new MojoFailureException(errMsg);
}
}
private AbstractLintingRule logFoundRule(final AbstractLintingRule lintingRule) {
final String message = "Linting rule <" + lintingRule.getClass().getName()
+ "> was detected in the classpath and will be used.";
getLog().debug(message);
return lintingRule;
}
private void reportsAndBreakBuildIfNecessary() throws MojoFailureException {
assert (this.reporter != null) : "Expected reporter to be set";
if (this.reporter.containsReports()) {
getLog().debug("Lint errors found.");
this.reporter
.getReports()
.forEach(report -> getLog().error(report));
if (this.failOnLintErrors) {
throw new MojoFailureException(
"The build contains lint errors. Please fix them then restart the build.");
}
} else {
getLog().debug("No lint errors found.");
}
}
private void retrieveAndSetRules() throws MojoExecutionException {
this.builtInRules = LintingRulesFinder
.findBuiltInLintingRules()
.stream()
.filter(LintingMojo::isNotCustomRule)
.map(this::buildAndConfigureBuiltInRule)
.filter(Optional::isPresent)
.map(Optional::get)
.map(this::logFoundRule)
.collect(Collectors.toSet());
this.customRules = LintingRulesFinder
.findCustomLintingRuleFactories()
.stream()
.map(this::buildAndConfigureCustomRules)
.filter(Optional::isPresent)
.map(Optional::get)
.map(this::logFoundRule)
.collect(Collectors.toSet());
if (this.builtInRules.isEmpty() && this.customRules.isEmpty()) {
throw new MojoExecutionException("No active rules to be found. Skipping.");
}
}
private void setReporter() {
this.reporter = new MojoReporter();
}
private void validateBuiltInRulesConfiguration() {
if (this.builtInRulesConfiguration == null) {
getLog().info("No configuration found for built-in rules. Falling back to defaults.");
this.builtInRulesConfiguration = new BuiltInRules();
}
}
private void validateCustomRulesConfiguration() {
if (this.customRulesConfiguration == null) {
getLog().debug("No configuration found for custom rules. Falling back to defaults.");
this.customRulesConfiguration = new Properties();
}
}
}