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

de.obqo.decycle.maven.AbstractDecycleMojo Maven / Gradle / Ivy

package de.obqo.decycle.maven;

import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;

import de.obqo.decycle.check.Constraint;
import de.obqo.decycle.check.DirectLayeringConstraint;
import de.obqo.decycle.check.Layer;
import de.obqo.decycle.check.LayeringConstraint;
import de.obqo.decycle.configuration.Configuration;
import de.obqo.decycle.configuration.Pattern;
import de.obqo.decycle.report.ResourcesExtractor;
import de.obqo.decycle.slicer.IgnoredDependency;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
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.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import lombok.AccessLevel;
import lombok.Setter;

@Setter(AccessLevel.PACKAGE)
abstract class AbstractDecycleMojo extends AbstractMojo {

    protected static final String MAIN = "main";
    protected static final String TEST = "test";

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

    /**
     * Comma separated list of inclusion patterns, for example org.company.package.**
     */
    @Parameter
    private String including;

    /**
     * Comma separated list of exclusion patterns, for example org.company.package.**
     */
    @Parameter
    private String excluding;

    /**
     * If set to true, then violations detected by decycle will not cause the build to fail. Default is false.
     */
    @Parameter(property = "decycle.ignoreFailures", defaultValue = "false")
    private boolean ignoreFailures;

    /**
     * List of ignored dependencies. Every element has a 'from' and a 'to' pattern describing the two sides of the
     * dependency. Omitting one of them is equivalent of specifying '**', i.e. dependencies from any or to any class
     * will be ignored. Example element:
     * 

* <value><from>org.company.model.**</from><to>org.company.service.Locator</to></value> */ @Parameter private Dependency[] ignoring; /** * List of slicing definitions. Each slicing has a name and a comma separated list of patterns. Example element: * <value><name>module</name><patterns>org.company.(*).**</patterns></value>. Each pattern is * either an unnamed pattern (like in the example above) or a named pattern having the form 'pattern=name' */ @Parameter private Slicing[] slicings; /** * If set to true, then the decycle checks will be skipped. Default is false. */ @Parameter(property = "decycle.skip", defaultValue = "false") private boolean skip; /** * If set to true, then the decycle check for the main classes will be skipped. Default is false. */ @Parameter(property = "decycle.skipMain", defaultValue = "false") private boolean skipMain; /** * If set to true, then the decycle check for the test classes will be skipped. Default is false. */ @Parameter(property = "decycle.skipTest", defaultValue = "false") private boolean skipTest; /** * If set to true, then no report is created after executing the decycle checks. Default is false. */ @Parameter(property = "decycle.skipReports", defaultValue = "false") private boolean skipReports; @Override public final void execute() throws MojoExecutionException, MojoFailureException { try { final List violations = executeCheck(); if (!this.ignoreFailures && !violations.isEmpty()) { throw new MojoFailureException("Decycle check failed"); } } catch (final IOException e) { throw new MojoExecutionException(e.getMessage(), e); } } protected abstract List executeCheck() throws IOException; protected List checkMain() throws IOException { if (this.skip || this.skipMain) { getLog().info("Skipped decycle check for main classes"); return List.of(); } return check(getMainClasses(), MAIN); } protected List checkTest() throws IOException { if (this.skip || this.skipTest) { getLog().info("Skipped decycle check for test classes"); return List.of(); } return check(getTestClasses(), TEST); } protected List check(final String classpath, final String sourceSet) throws IOException { final Log log = getLog(); if (!new File(classpath).exists()) { log.warn("Decycle: " + classpath + " is missing - skipped decycle check for " + sourceSet + " classes"); return List.of(); } if (this.skipReports) { final Configuration config = buildConfiguration(classpath, sourceSet, null, null); return check(config, null); } else { final File reportDir = getDecycleReportDir(); final String resourcesDirName = ResourcesExtractor.createResourcesIfRequired(reportDir); final File report = new File(reportDir, sourceSet + ".html"); try (final FileWriter reportWriter = new FileWriter(report)) { final Configuration config = buildConfiguration(classpath, sourceSet, resourcesDirName, reportWriter); return check(config, report); } } } // visible for testing Configuration buildConfiguration(final String classpath, final String sourceSet, final String resourcesDirName, final FileWriter reportWriter) { return Configuration.builder() .classpath(classpath) .including(tokenizeToList(this.including)) .excluding(tokenizeToList(this.excluding)) .ignoring(getIgnoredDependencies()) .slicings(getSlicings()) .constraints(getConstraints()) .report(reportWriter) .reportResourcesPrefix(resourcesDirName) .reportTitle(this.project.getName() + " | " + sourceSet) .build(); } private List check(final Configuration config, final File report) { final Log log = getLog(); log.debug("Decycle configuration: " + config); final Consumer logHandler = this.ignoreFailures ? log::warn : log::error; final List violations = config.check(); if (!violations.isEmpty()) { logHandler.accept("Violations detected: " + Constraint.Violation.displayString(violations)); if (report != null) { logHandler.accept("See the report at: " + report); } } return violations; } protected String getMainClasses() { return this.project.getBuild().getOutputDirectory(); } protected String getTestClasses() { return this.project.getBuild().getTestOutputDirectory(); } private File getDecycleReportDir() { return new File(this.project.getModel().getReporting().getOutputDirectory(), "decycle"); } private Stream tokenize(final String value) { return Optional.ofNullable(value).map(v -> v.split(",")).stream() .flatMap(Arrays::stream) .map(String::trim) .filter(not(String::isEmpty)); } private List tokenizeToList(final String value) { return tokenize(value).collect(toList()); } private String[] tokenizeToArray(final String value) { return tokenize(value).toArray(String[]::new); } private List getIgnoredDependencies() { return stream(this.ignoring) .map(dependency -> IgnoredDependency.create(dependency.getFrom(), dependency.getTo())) .collect(toList()); } private Map> getSlicings() { return stream(this.slicings).collect(Collectors.toMap( Slicing::getName, slicing -> tokenize(slicing.getPatterns()).map(Pattern::parse).collect(toList()))); } private Set getConstraints() { return stream(this.slicings).flatMap(this::mapConstraints).collect(toCollection(LinkedHashSet::new)); } private Stream mapConstraints(final Slicing slicing) { return stream(slicing.getConstraints()).map(constraint -> mapConstraint(slicing, constraint)); } private Constraint mapConstraint(final Slicing slicing, final AllowConstraint constraint) { final List layers; // The constraint configuration is either simple or complex (never mixed) if (constraint.get() != null) { // this is a simple configuration using slice1, slice2, ... layers = tokenize(constraint.get()).map(Layer::anyOf).collect(toList()); } else { // this is a complex configuration using slice1, slice2... layers = constraint.getLayers().stream().map(this::mapLayers).collect(toList()); } return constraint.isDirect() ? new DirectLayeringConstraint(slicing.getName(), layers) : new LayeringConstraint(slicing.getName(), layers); } private Layer mapLayers(final de.obqo.decycle.maven.Layer layer) { final String[] slices = tokenizeToArray(layer.getSlices()); return layer.isStrict() ? Layer.oneOf(slices) : Layer.anyOf(slices); } private Stream stream(final T[] array) { return Optional.ofNullable(array).stream().flatMap(Arrays::stream); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy