All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.pmd.AbstractPmdViolationCheckMojo Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.plugins.pmd;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

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;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Base class for mojos that check if there were any PMD violations.
 *
 * @param  type of the check, e.g. {@link org.apache.maven.plugins.pmd.model.Violation}
 * or {@link org.apache.maven.plugins.pmd.model.Duplication}.
 * @author Brett Porter
 * @version $Id$
 */
public abstract class AbstractPmdViolationCheckMojo extends AbstractMojo {
    /**
     * The location of the XML report to check, as generated by the PMD report.
     */
    @Parameter(property = "project.build.directory", required = true)
    private File targetDirectory;

    /**
     * Whether to fail the build if the validation check fails.
     * The properties {@code failurePriority} and {@code maxAllowedViolations} control
     * under which conditions exactly the build should be failed.
     */
    @Parameter(property = "pmd.failOnViolation", defaultValue = "true", required = true)
    protected boolean failOnViolation;

    /**
     * Whether to build an aggregated report at the root, or build individual reports.
     *
     * @since 2.2
     * @deprecated since 3.15.0 Use the goal pmd:aggregate-check or
     * pmd:aggregate-cpd-check instead.
     */
    @Parameter(property = "aggregate", defaultValue = "false")
    @Deprecated
    protected boolean aggregate;

    /**
     * Print details of check failures to build output.
     */
    @Parameter(property = "pmd.verbose", defaultValue = "false")
    private boolean verbose;

    /**
     * Print details of errors that cause build failure
     *
     * @since 3.0
     */
    @Parameter(property = "pmd.printFailingErrors", defaultValue = "false")
    private boolean printFailingErrors;

    /**
     * File that lists classes and rules to be excluded from failures.
     * For PMD, this is a properties file. For CPD, this
     * is a text file that contains comma-separated lists of classes
     * that are allowed to duplicate.
     *
     * @since 3.0
     */
    @Parameter(property = "pmd.excludeFromFailureFile", defaultValue = "")
    private String excludeFromFailureFile;

    /**
     * The maximum number of failures allowed before execution fails.
     * Used in conjunction with {@code failOnViolation=true} and utilizes {@code failurePriority}.
     * This value has no meaning if {@code failOnViolation=false}.
     * If the number of failures is greater than this number, the build will be failed.
     * If the number of failures is less than or equal to this value,
     * then the build will not be failed.
     *
     * @since 3.10.0
     */
    @Parameter(property = "pmd.maxAllowedViolations", defaultValue = "0")
    private int maxAllowedViolations;

    /** Helper to exclude violations from the result. */
    private final ExcludeFromFile excludeFromFile;

    /**
     * Initialize this abstact check mojo by giving the correct ExcludeFromFile helper.
     * @param excludeFromFile the needed helper, for the specific violation type
     */
    protected AbstractPmdViolationCheckMojo(ExcludeFromFile excludeFromFile) {
        this.excludeFromFile = excludeFromFile;
    }

    /**
     * The project to analyze.
     */
    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    protected MavenProject project;

    protected void executeCheck(
            final String filename, final String analyzerName, final String failureName, final int failurePriority)
            throws MojoFailureException, MojoExecutionException {
        if (aggregate && !project.isExecutionRoot()) {
            return;
        }

        if (!isAggregator() && "pom".equalsIgnoreCase(project.getPackaging())) {
            return;
        }

        excludeFromFile.loadExcludeFromFailuresData(excludeFromFailureFile);
        final File outputFile = new File(targetDirectory, filename);

        if (outputFile.exists()) {
            try {
                final ViolationDetails violations = getViolations(outputFile, failurePriority);

                final List failures = violations.getFailureDetails();
                final List warnings = violations.getWarningDetails();

                if (verbose) {
                    printErrors(failures, warnings);
                }

                final int failureCount = failures.size();
                final int warningCount = warnings.size();

                final String message = getMessage(failureCount, warningCount, analyzerName, failureName, outputFile);

                if (failureCount > getMaxAllowedViolations() && isFailOnViolation()) {
                    throw new MojoFailureException(message);
                }

                if (!message.isEmpty()) {
                    this.getLog().warn(message);
                }

                if (failureCount > 0 && isFailOnViolation() && failureCount <= getMaxAllowedViolations()) {
                    this.getLog()
                            .info("The build has not failed because " + getMaxAllowedViolations()
                                    + " violations are allowed (maxAllowedViolations).");
                }
            } catch (final IOException | XmlPullParserException e) {
                throw new MojoExecutionException(
                        "Unable to read " + analyzerName + " results XML: " + outputFile.getAbsolutePath(), e);
            }
        } else {
            throw new MojoFailureException("Unable to perform check, " + "unable to find " + outputFile);
        }
    }

    /**
     * Method for collecting the violations found by the PMD tool
     *
     * @param analysisFile
     * @param failurePriority
     * @return an int that specifies the number of violations found
     * @throws XmlPullParserException
     * @throws IOException
     */
    private ViolationDetails getViolations(final File analysisFile, final int failurePriority)
            throws XmlPullParserException, IOException {
        final List failures = new ArrayList<>();
        final List warnings = new ArrayList<>();

        final List violations = getErrorDetails(analysisFile);

        for (final D violation : violations) {
            final int priority = getPriority(violation);
            if (priority <= failurePriority && !excludeFromFile.isExcludedFromFailure(violation)) {
                failures.add(violation);
                if (printFailingErrors) {
                    printError(violation, "Failure");
                }
            } else {
                warnings.add(violation);
            }
        }

        final ViolationDetails details = newViolationDetailsInstance();
        details.setFailureDetails(failures);
        details.setWarningDetails(warnings);
        return details;
    }

    protected abstract int getPriority(D errorDetail);

    protected abstract ViolationDetails newViolationDetailsInstance();

    /**
     * Prints the warnings and failures
     *
     * @param failures list of failures
     * @param warnings list of warnings
     */
    protected void printErrors(final List failures, final List warnings) {
        for (final D warning : warnings) {
            printError(warning, "Warning");
        }

        for (final D failure : failures) {
            printError(failure, "Failure");
        }
    }

    /**
     * Gets the output message
     *
     * @param failureCount
     * @param warningCount
     * @param analyzerName
     * @param failureName
     * @param outputFile
     * @return
     */
    private String getMessage(
            final int failureCount,
            final int warningCount,
            final String analyzerName,
            final String failureName,
            final File outputFile) {
        final StringBuilder message = new StringBuilder(256);
        if (failureCount > 0 || warningCount > 0) {
            if (failureCount > 0) {
                message.append(analyzerName)
                        .append(" ")
                        .append(AbstractPmdReport.getPmdVersion())
                        .append(" has found ")
                        .append(failureCount)
                        .append(" ")
                        .append(failureName)
                        .append(failureCount > 1 ? "s" : "");
            }

            if (warningCount > 0) {
                if (failureCount > 0) {
                    message.append(" and issued ");
                } else {
                    message.append(analyzerName)
                            .append(" ")
                            .append(AbstractPmdReport.getPmdVersion())
                            .append(" has issued ");
                }
                message.append(warningCount).append(" warning").append(warningCount > 1 ? "s" : "");
            }

            message.append(". For more details see: ").append(outputFile.getAbsolutePath());
        }
        return message.toString();
    }

    /**
     * Formats the failure details and prints them as an INFO message
     *
     * @param item either a {@link org.apache.maven.plugins.pmd.model.Violation} from PMD
     * or a {@link org.apache.maven.plugins.pmd.model.Duplication} from CPD
     * @param severity the found issue is prefixed with the given severity, usually "Warning" or "Failure".
     */
    protected abstract void printError(D item, String severity);

    /**
     * Gets the attributes and text for the violation tag and puts them in a HashMap
     *
     * @param analysisFile the xml output from PMD or CPD
     * @return all PMD {@link org.apache.maven.plugins.pmd.model.Violation}s
     * or CPD {@link org.apache.maven.plugins.pmd.model.Duplication}s.
     * @throws XmlPullParserException if the analysis file contains invalid XML
     * @throws IOException if the analysis file could be read
     */
    protected abstract List getErrorDetails(File analysisFile) throws XmlPullParserException, IOException;

    public boolean isFailOnViolation() {
        return failOnViolation;
    }

    public Integer getMaxAllowedViolations() {
        return maxAllowedViolations;
    }

    protected boolean isAggregator() {
        // returning here aggregate for backwards compatibility
        return aggregate;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy