JDependReportRenderer.java

package com.reallifedeveloper.maven.jdepend;

import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Supplier;

import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.reporting.AbstractMavenReportRenderer;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import com.reallifedeveloper.maven.jdepend.xml.XmlReport;
import com.reallifedeveloper.maven.jdepend.xml.XmlReport.XmlClass;
import com.reallifedeveloper.maven.jdepend.xml.XmlReport.XmlPackage;
import com.reallifedeveloper.maven.jdepend.xml.XmlReport.XmlPackageWithCycle;
import com.reallifedeveloper.maven.jdepend.xml.XmlReport.XmlStats;

/**
 * A {@code MavenReportRenderer} that creates a Maven report based on an {@link XmlReport}.
 *
 * @author RealLifeDeveloper
 */
public class JDependReportRenderer extends AbstractMavenReportRenderer {

    private static final int JUSTIFY_CENTER = 0;
    private static final int JUSTIFY_LEFT = 1;
    private static final int CENT = 100;

    private final XmlReport xmlReport;
    private final ResourceBundle bundle;
    private final List<XmlPackage> packagesToReport;

    /**
     * Creates a new {@code JDependReportRenderer}.
     *
     * @param xmlReport the {@link XmlReport} to use as basis for the report
     * @param bundle    the {@code ResourceBundle} to use to translate the report to different languages
     * @param sink      the {@link Sink} to use to produce markup for the report
     */
    @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "ResourceBundle is motable, but that is OK")
    public JDependReportRenderer(XmlReport xmlReport, ResourceBundle bundle, Sink sink) {
        super(sink);
        this.xmlReport = xmlReport;
        this.bundle = bundle;
        this.packagesToReport = xmlReport.packagesWithoutError();
    }

    @Override
    public String getTitle() {
        return bundle.getString("report.title");
    }

    @Override
    protected void renderBody() {
        startSection(getTitle());
        doIntroSection();
        doSummarySection();
        doPackagesSection();
        doExplanationSection();
        endSection();
    }

    private void doIntroSection() {
        sink.rawText(bundle.getString("report.intro"));
        sink.lineBreak();
        sink.lineBreak();
    }

    private void doSummarySection() {
        startSection(bundle.getString("report.summary.title"));
        startTable(new int[] { JUSTIFY_LEFT, JUSTIFY_CENTER, JUSTIFY_CENTER, JUSTIFY_CENTER, JUSTIFY_CENTER, JUSTIFY_CENTER, JUSTIFY_CENTER,
                JUSTIFY_CENTER, JUSTIFY_CENTER, JUSTIFY_CENTER, JUSTIFY_CENTER, JUSTIFY_CENTER }, true);
        tableHeader(new String[] { bundle.getString("report.package"), bundle.getString("report.TC"), bundle.getString("report.CC"),
                bundle.getString("report.AC"), bundle.getString("report.Ca"), bundle.getString("report.Ce"), bundle.getString("report.A"),
                bundle.getString("report.I"), bundle.getString("report.D"), bundle.getString("report.cycles"),
                bundle.getString("report.package-info") });
        for (XmlPackage xmlPackage : packagesToReport) {
            XmlStats stats = xmlPackage.stats();
            sink.tableRow();
            sink.tableCell();
            sink.link("#" + xmlPackage.name()); // $NON-NLS-1$
            text(xmlPackage.name());
            sink.link_();
            sink.tableCell_();
            tableCell(Integer.toString(stats.totalClasses()));
            tableCell(Integer.toString(stats.concreteClasses()));
            tableCell(Integer.toString(stats.abstractClasses()));
            tableCell(Integer.toString(stats.afferentCouplings()));
            tableCell(Integer.toString(stats.efferentCouplings()));
            tableCell(convertToPercentString(stats.abstractness()));
            tableCell(convertToPercentString(stats.instability()));
            tableCell(convertToPercentString(stats.distance()));
            boolean hasCycles = xmlReport.findPackageWithCycle(xmlPackage.name()).map(p -> !p.packagesInCycle().isEmpty()).orElse(false);
            tableCell(Boolean.toString(hasCycles));
            tableCell(Boolean.toString(stats.hasPackageInfo()));
            sink.tableRow_();
        }
        endTable();
        endSection();
    }

    private static String convertToPercentString(double value) {
        return String.format("%.0f%%", value * CENT);
    }

    private void doPackagesSection() {
        startSection(bundle.getString("report.packages"));
        if (packagesToReport.isEmpty()) {
            text(bundle.getString("report.nopackages"));
        } else {
            for (XmlPackage xmlPackage : packagesToReport) {
                startSection(xmlPackage.name());

                startSection(bundle.getString("report.abstractclasses"));
                addListOrDefaultText(() -> xmlPackage.abstractClasses().stream().map(XmlClass::name).toList(), "");
                endSection();

                startSection(bundle.getString("report.concreteclasses"));
                addListOrDefaultText(() -> xmlPackage.concreteClasses().stream().map(XmlClass::name).toList(), "");
                endSection();

                startSection(bundle.getString("report.usedbypackages"));
                addListOrDefaultText(xmlPackage::usedBy, "");
                endSection();

                startSection(bundle.getString("report.usespackage"));
                addListOrDefaultText(xmlPackage::dependsUpon, "");
                endSection();

                startSection(bundle.getString("report.cycles"));
                Optional<XmlPackageWithCycle> cycles = xmlReport.findPackageWithCycle(xmlPackage.name());
                if (cycles.isEmpty()) {
                    text(bundle.getString("report.nocyclicdependencies"));
                    sink.lineBreak();
                    sink.lineBreak();
                } else {
                    addListOrDefaultText(() -> cycles.get().packagesInCycle(), "");
                    sink.lineBreak();
                }
                endSection();

                endSection();
            }

        }
        endSection();
    }

    private void addListOrDefaultText(Supplier<List<String>> stringSupplier, String defaultText) {
        List<String> strings = stringSupplier.get();
        if (strings.isEmpty()) {
            text(defaultText);
        } else {
            sink.list();
            for (String string : strings) {
                sink.listItem();
                text(string);
                sink.listItem_();
            }
            sink.list_();
        }
    }

    private void doExplanationSection() {
        startSection(bundle.getString("report.explanation.title"));
        sink.rawText(bundle.getString("report.explanation.description"));
        sink.lineBreak();
        sink.lineBreak();
        startTable(new int[] { JUSTIFY_LEFT, JUSTIFY_LEFT }, true);
        tableHeader(new String[] { bundle.getString("report.term"), bundle.getString("report.description") });
        tableRow(new String[] { bundle.getString("report.numberofclasses.title"), bundle.getString("report.numberofclasses.description") });
        tableRow(new String[] { bundle.getString("report.afferentcouplings.title"),
                bundle.getString("report.afferentcouplings.description") });
        tableRow(new String[] { bundle.getString("report.efferentcouplings.title"),
                bundle.getString("report.efferentcouplings.description") });
        tableRow(new String[] { bundle.getString("report.abstractness.title"), bundle.getString("report.abstractness.description") });
        tableRow(new String[] { bundle.getString("report.instability.title"), bundle.getString("report.instability.description") });
        tableRow(new String[] { bundle.getString("report.distance.title"), bundle.getString("report.distance.description") });
        tableRow(new String[] { bundle.getString("report.cycles.title"), bundle.getString("report.cycles.description") });
        tableRow(new String[] { bundle.getString("report.packageinfo.title"), bundle.getString("report.packageinfo.description") });
        endTable();
        endSection();
    }

}