AbstractParentMojo.java

package org.thewonderlemming.c4plantuml.mojo;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

/**
 * A base implementation of a MOJO that handles shared parameters such as {@link Charset}, {@link MavenProject}, and so
 * on.
 *
 * @author thewonderlemming
 *
 */
public abstract class AbstractParentMojo extends AbstractMojo {

    /**
     * Default value for the C4 PlantUML files extension.
     */
    public static final String DEFAULT_SOURCE_FILE_EXTENSION = ".puml";

    private static final Pattern SOURCE_FILE_EXTENSION_PATTERN = Pattern.compile("^\\.[a-zA-Z0-9]+$");

    private Charset currentCharset;

    private String outputDirectory;

    @Parameter(property = "project", defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;

    @Parameter(property = "sourceFileExtension", defaultValue = AbstractParentMojo.DEFAULT_SOURCE_FILE_EXTENSION,
        alias = "sourceFileExtension")
    private String sourceFileExtension;


    private static boolean isFile(final Path path) {
        return path.toFile().isFile();
    }

    /**
     * The default implementation of {@link AbstractMojo#execute()}, that sets and validates shared parameters, then
     * calls the abstract method {@link AbstractParentMojo#doExecute()}, that needs to be implemented by children
     * classes.
     * <p>
     * {@inheritDoc}
     */
    @Override
    public final void execute() throws MojoExecutionException, MojoFailureException {

        validateSourceFileExtension();
        setCurrentCharset();
        setOutputDirectory();

        doExecute();
    }

    /**
     * An abstract method to hold the children classes execution logic.
     *
     * @throws MojoExecutionException if there is an issue with the build that makes it impossible to continue.
     * @throws MojoFailureException if there is any other kind of failures.
     */
    protected abstract void doExecute() throws MojoExecutionException, MojoFailureException;

    /**
     * Returns the current {@link Charset} value.
     *
     * @return the current {@link Charset}.
     */
    protected Charset getCurrentCharset() {
        return currentCharset;
    }

    /**
     * Returns the project output directory path.
     *
     * @return the project output directory.
     */
    protected String getOutputDirectory() {
        return this.outputDirectory;
    }

    /**
     * Returns the current {@link MavenProject} instance.
     *
     * @return the {@link MavenProject} instance.
     */
    protected MavenProject getProject() {
        return this.project;
    }

    /**
     * Returns the current sources filename extension.
     * <p>
     * This is how the plugin will determine which files it should process.
     *
     * @return the sources filename extension.
     */
    protected String getSourceFileExtension() {
        return this.sourceFileExtension;
    }

    /**
     * An utility method that enables the processing of the C4 PlantUML sources in the output directory.
     *
     * @param pumlFileProcessor the source file consumer method.
     * @throws IOException if any I/O error happens while accessing a source file.
     */
    protected void processOutputDirectoryFiles(final Consumer<Path> pumlFileProcessor) throws IOException {

        try (final Stream<Path> pathStream = Files.walk(Paths.get(getOutputDirectory()))) {

            pathStream
                .filter(AbstractParentMojo::isFile)
                    .filter(this::isPumlSource)
                    .forEach(pumlFileProcessor::accept);
        }
    }

    /**
     * Validates that the current {@code sourceFileExtension} property has a correct value, or sets it to the default
     * value and displays an error message.
     */
    protected void validateSourceFileExtension() {

        assert (this.sourceFileExtension != null) : "Expecting sourceFileExtension to be set";

        if (!SOURCE_FILE_EXTENSION_PATTERN.matcher(this.sourceFileExtension).matches()) {

            final String errMsg = String
                .format("Bad format for sourceFileExtension: %s. Reverting to default value instead: %s",
                    this.sourceFileExtension,
                    DEFAULT_SOURCE_FILE_EXTENSION);

            getLog().warn(errMsg);
            this.sourceFileExtension = DEFAULT_SOURCE_FILE_EXTENSION;
        }
    }

    private boolean isPumlSource(final Path path) {

        assert (this.sourceFileExtension != null) : "Expecting sourceFileExtension to be set";
        return path.toString().toLowerCase().endsWith(this.sourceFileExtension);
    }

    private void setCurrentCharset() throws MojoExecutionException {

        try {

            final String sourceEncoding = this.project
                .getProperties()
                    .getProperty("project.build.sourceEncoding", StandardCharsets.UTF_8.toString());

            this.currentCharset = Charset.forName(sourceEncoding);

        } catch (final IllegalArgumentException e) {
            throw new MojoExecutionException("Cannot get character encoding for source files", e);
        }
    }

    private void setOutputDirectory() throws MojoExecutionException {

        this.outputDirectory = getProject().getBuild().getOutputDirectory();

        if (!new File(this.outputDirectory).exists()) {

            final String errMsg = "Cannot process source directory \"" + this.outputDirectory
                + "\" as it does not exist";
            throw new MojoExecutionException(errMsg);
        }
    }
}