# 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();
            }
        }
    }