XmlReport.java

package com.reallifedeveloper.maven.jdepend.xml;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlValue;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * A representation of the XML report generated by JDepend.
 *
 * @author RealLifeDeveloper
 */
@XmlRootElement(name = "JDepend")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
@Accessors(fluent = true)
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The classes here are mutable for convenience; we can simply use lombok.Data")
public class XmlReport {

    @XmlElementWrapper(name = "Packages")
    @XmlElement(name = "Package")
    private List<XmlPackage> packages = new ArrayList<>();

    @XmlElementWrapper(name = "Cycles")
    @XmlElement(name = "Package")
    private List<XmlPackageWithCycle> cycles = new ArrayList<>();

    /**
     * Gives the packages that were successfully analyzed by JDepend.
     * <p>
     * The XML report generataed by JDepend includes external packages, e.g., {@code java.lang}, but with an error message saying something
     * like {@code package referenced, but not analyzed}.
     *
     * @return the packages that were successfully analyzed by JDepend
     */
    public List<XmlPackage> packagesWithoutError() {
        return packages().stream().filter(p -> p.error() == null).toList();
    }

    /**
     * Gives the packages that were included in the JDepend report, but not successfully analyzed.
     *
     * @return the packages that were included in the JDepend report, but not successfully analyzed
     */
    public List<XmlPackage> packagesWithError() {
        return packages().stream().filter(p -> p.error() != null).toList();
    }

    /**
     * If the given package has any cycles, provides information about this.
     *
     * @param packageName the name of the package to check
     *
     * @return an optional containing an {@link XmlPackageWithCycle} if the package named {@code packageName} contains cycles, an empty
     *         optional otherwise
     */
    public Optional<XmlPackageWithCycle> findPackageWithCycle(String packageName) {
        return cycles().stream().filter(c -> packageName.equals(c.name())).findFirst();
    }

    /**
     * Contains information from the {@code Package} element.
     */
    @Data
    public static class XmlPackage {
        @XmlAttribute(name = "name")
        private String name;

        @XmlElement(name = "Stats")
        private XmlStats stats;

        @XmlElementWrapper(name = "AbstractClasses")
        @XmlElement(name = "Class")
        private List<XmlClass> abstractClasses = new ArrayList<>();

        @XmlElementWrapper(name = "ConcreteClasses")
        @XmlElement(name = "Class")
        private List<XmlClass> concreteClasses = new ArrayList<>();

        @XmlElementWrapper(name = "DependsUpon")
        @XmlElement(name = "Package")
        private List<String> dependsUpon = new ArrayList<>();

        @XmlElementWrapper(name = "UsedBy")
        @XmlElement(name = "Package")
        private List<String> usedBy = new ArrayList<>();

        @XmlElement(name = "error")
        private String error;
    }

    /**
     * Contains information from the {@code Stats} element.
     */
    @Data
    public static class XmlStats {
        @XmlElement(name = "TotalClasses")
        private int totalClasses;

        @XmlElement(name = "ConcreteClasses")
        private int concreteClasses;

        @XmlElement(name = "AbstractClasses")
        private int abstractClasses;

        @XmlElement(name = "HasPackageInfo")
        private boolean hasPackageInfo;

        @XmlElement(name = "Ca")
        private int afferentCouplings;

        @XmlElement(name = "Ce")
        private int efferentCouplings;

        @XmlElement(name = "A")
        private double abstractness;

        @XmlElement(name = "I")
        private double instability;

        @XmlElement(name = "D")
        private double distance;

        @XmlElement(name = "V")
        private int volatility;
    }

    /**
     * Contains information from the {@code Class} element.
     */
    @Data
    public static class XmlClass {
        @XmlAttribute(name = "sourceFile")
        private String sourceFile;

        @XmlValue
        private String name;
    }

    /**
     * Contains information from the {@code Cycles} element.
     */
    @Data
    public static class XmlPackageWithCycle {
        @XmlAttribute(name = "Name")
        private String name;

        @XmlElement(name = "Package")
        private List<String> packagesInCycle = new ArrayList<>();
    }
}