org.apache.maven.report.projectinfo.dependencies.renderer.DependenciesRenderer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.maven.report.projectinfo.dependencies.renderer.DependenciesRenderer.java

Source

package org.apache.maven.report.projectinfo.dependencies.renderer;

/*
 * 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.
 */

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.lang.SystemUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.SinkEventAttributeSet;
import org.apache.maven.doxia.sink.SinkEventAttributes;
import org.apache.maven.doxia.util.HtmlTools;
import org.apache.maven.model.License;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.report.projectinfo.AbstractProjectInfoRenderer;
import org.apache.maven.report.projectinfo.ProjectInfoReportUtils;
import org.apache.maven.report.projectinfo.dependencies.Dependencies;
import org.apache.maven.report.projectinfo.dependencies.DependenciesReportConfiguration;
import org.apache.maven.report.projectinfo.dependencies.RepositoryUtils;
import org.apache.maven.settings.Settings;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.jar.JarData;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.util.StringUtils;

/**
 * Renderer the dependencies report.
 *
 * @version $Id$
 * @since 2.1
 */
public class DependenciesRenderer extends AbstractProjectInfoRenderer {
    /** URL for the 'icon_info_sml.gif' image */
    private static final String IMG_INFO_URL = "./images/icon_info_sml.gif";

    /** URL for the 'close.gif' image */
    private static final String IMG_CLOSE_URL = "./images/close.gif";

    /** Used to format decimal values in the "Dependency File Details" table */
    protected static final DecimalFormat DEFAULT_DECIMAL_FORMAT = new DecimalFormat("###0");

    private static final Set<String> JAR_SUBTYPE;

    /**
     * An HTML script tag with the Javascript used by the dependencies report.
     */
    private static final String JAVASCRIPT;

    private final DependencyNode dependencyNode;

    private final Dependencies dependencies;

    private final DependenciesReportConfiguration configuration;

    private final Log log;

    private final Settings settings;

    private final RepositoryUtils repoUtils;

    /** Used to format file length values */
    private final DecimalFormat fileLengthDecimalFormat;

    /**
     * @since 2.1.1
     */
    private int section;

    /** Counter for unique IDs that is consistent across generations. */
    private int idCounter = 0;

    /**
     * Will be filled with license name / set of projects.
     */
    private Map<String, Object> licenseMap = new HashMap<String, Object>() {
        private static final long serialVersionUID = 1L;

        /** {@inheritDoc} */
        public Object put(String key, Object value) {
            // handle multiple values as a set to avoid duplicates
            @SuppressWarnings("unchecked")
            SortedSet<Object> valueList = (SortedSet<Object>) get(key);
            if (valueList == null) {
                valueList = new TreeSet<Object>();
            }
            valueList.add(value);
            return super.put(key, valueList);
        }
    };

    private final ArtifactFactory artifactFactory;

    private final MavenProjectBuilder mavenProjectBuilder;

    private final List<ArtifactRepository> remoteRepositories;

    private final ArtifactRepository localRepository;

    static {
        Set<String> jarSubtype = new HashSet<String>();
        jarSubtype.add("jar");
        jarSubtype.add("war");
        jarSubtype.add("ear");
        jarSubtype.add("sar");
        jarSubtype.add("rar");
        jarSubtype.add("par");
        jarSubtype.add("ejb");
        JAR_SUBTYPE = Collections.unmodifiableSet(jarSubtype);

        JAVASCRIPT = "<script language=\"javascript\" type=\"text/javascript\">" + SystemUtils.LINE_SEPARATOR
                + "      function toggleDependencyDetail( divId, imgId )" + SystemUtils.LINE_SEPARATOR + "      {"
                + SystemUtils.LINE_SEPARATOR + "        var div = document.getElementById( divId );"
                + SystemUtils.LINE_SEPARATOR + "        var img = document.getElementById( imgId );"
                + SystemUtils.LINE_SEPARATOR + "        if( div.style.display == '' )" + SystemUtils.LINE_SEPARATOR
                + "        {" + SystemUtils.LINE_SEPARATOR + "          div.style.display = 'none';"
                + SystemUtils.LINE_SEPARATOR + "          img.src='" + IMG_INFO_URL + "';"
                + SystemUtils.LINE_SEPARATOR + "        }" + SystemUtils.LINE_SEPARATOR + "        else"
                + SystemUtils.LINE_SEPARATOR + "        {" + SystemUtils.LINE_SEPARATOR
                + "          div.style.display = '';" + SystemUtils.LINE_SEPARATOR + "          img.src='"
                + IMG_CLOSE_URL + "';" + SystemUtils.LINE_SEPARATOR + "        }" + SystemUtils.LINE_SEPARATOR
                + "      }" + SystemUtils.LINE_SEPARATOR + "</script>" + SystemUtils.LINE_SEPARATOR;
    }

    /**
     * Default constructor.
     *
     * @param sink
     * @param locale
     * @param i18n
     * @param log
     * @param settings
     * @param dependencies
     * @param dependencyTreeNode
     * @param config
     * @param repoUtils
     * @param artifactFactory
     * @param mavenProjectBuilder
     * @param remoteRepositories
     * @param localRepository
     */
    public DependenciesRenderer(Sink sink, Locale locale, I18N i18n, Log log, Settings settings,
            Dependencies dependencies, DependencyNode dependencyTreeNode, DependenciesReportConfiguration config,
            RepositoryUtils repoUtils, ArtifactFactory artifactFactory, MavenProjectBuilder mavenProjectBuilder,
            List<ArtifactRepository> remoteRepositories, ArtifactRepository localRepository) {
        super(sink, i18n, locale);

        this.log = log;
        this.settings = settings;
        this.dependencies = dependencies;
        this.dependencyNode = dependencyTreeNode;
        this.repoUtils = repoUtils;
        this.configuration = config;
        this.artifactFactory = artifactFactory;
        this.mavenProjectBuilder = mavenProjectBuilder;
        this.remoteRepositories = remoteRepositories;
        this.localRepository = localRepository;

        // Using the right set of symbols depending of the locale
        DEFAULT_DECIMAL_FORMAT.setDecimalFormatSymbols(new DecimalFormatSymbols(locale));

        this.fileLengthDecimalFormat = new FileDecimalFormat(i18n, locale);
        this.fileLengthDecimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(locale));
    }

    @Override
    protected String getI18Nsection() {
        return "dependencies";
    }

    // ----------------------------------------------------------------------
    // Public methods
    // ----------------------------------------------------------------------

    @Override
    public void renderBody() {
        // Dependencies report

        if (!dependencies.hasDependencies()) {
            startSection(getTitle());

            // TODO: should the report just be excluded?
            paragraph(getI18nString("nolist"));

            endSection();

            return;
        }

        // === Section: Project Dependencies.
        renderSectionProjectDependencies();

        // === Section: Project Transitive Dependencies.
        renderSectionProjectTransitiveDependencies();

        // === Section: Project Dependency Graph.
        renderSectionProjectDependencyGraph();

        // === Section: Licenses
        renderSectionDependencyLicenseListing();

        if (configuration.getDependencyDetailsEnabled()) {
            // === Section: Dependency File Details.
            renderSectionDependencyFileDetails();
        }

        if (configuration.getDependencyLocationsEnabled()) {
            // === Section: Dependency Repository Locations.
            renderSectionDependencyRepositoryLocations();
        }
    }

    // ----------------------------------------------------------------------
    // Protected methods
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    // workaround for MPIR-140
    // TODO Remove me when maven-reporting-impl:2.1-SNAPSHOT is out
    protected void startSection(String name) {
        startSection(name, name);
    }

    /**
     * Start section with a name and a specific anchor.
     *
     * @param anchor not null
     * @param name not null
     */
    protected void startSection(String anchor, String name) {
        section = section + 1;

        super.sink.anchor(HtmlTools.encodeId(anchor));
        super.sink.anchor_();

        switch (section) {
        case 1:
            sink.section1();
            sink.sectionTitle1();
            break;
        case 2:
            sink.section2();
            sink.sectionTitle2();
            break;
        case 3:
            sink.section3();
            sink.sectionTitle3();
            break;
        case 4:
            sink.section4();
            sink.sectionTitle4();
            break;
        case 5:
            sink.section5();
            sink.sectionTitle5();
            break;

        default:
            // TODO: warning - just don't start a section
            break;
        }

        text(name);

        switch (section) {
        case 1:
            sink.sectionTitle1_();
            break;
        case 2:
            sink.sectionTitle2_();
            break;
        case 3:
            sink.sectionTitle3_();
            break;
        case 4:
            sink.sectionTitle4_();
            break;
        case 5:
            sink.sectionTitle5_();
            break;

        default:
            // TODO: warning - just don't start a section
            break;
        }
    }

    /** {@inheritDoc} */
    // workaround for MPIR-140
    // TODO Remove me when maven-reporting-impl:2.1-SNAPSHOT is out
    protected void endSection() {
        switch (section) {
        case 1:
            sink.section1_();
            break;
        case 2:
            sink.section2_();
            break;
        case 3:
            sink.section3_();
            break;
        case 4:
            sink.section4_();
            break;
        case 5:
            sink.section5_();
            break;

        default:
            // TODO: warning - just don't start a section
            break;
        }

        section = section - 1;

        if (section < 0) {
            throw new IllegalStateException("Too many closing sections");
        }
    }

    // ----------------------------------------------------------------------
    // Private methods
    // ----------------------------------------------------------------------

    /**
     * @param withClassifier <code>true</code> to include the classifier column, <code>false</code> otherwise.
     * @param withOptional <code>true</code> to include the optional column, <code>false</code> otherwise.
     * @return the dependency table header with/without classifier/optional column
     * @see #renderArtifactRow(Artifact, boolean, boolean)
     */
    private String[] getDependencyTableHeader(boolean withClassifier, boolean withOptional) {
        String groupId = getI18nString("column.groupId");
        String artifactId = getI18nString("column.artifactId");
        String version = getI18nString("column.version");
        String classifier = getI18nString("column.classifier");
        String type = getI18nString("column.type");
        String license = getI18nString("column.license");
        String optional = getI18nString("column.optional");

        if (withClassifier) {
            if (withOptional) {
                return new String[] { groupId, artifactId, version, classifier, type, license, optional };
            }

            return new String[] { groupId, artifactId, version, classifier, type, license };
        }

        if (withOptional) {
            return new String[] { groupId, artifactId, version, type, license, optional };
        }

        return new String[] { groupId, artifactId, version, type, license };
    }

    private void renderSectionProjectDependencies() {
        startSection(getTitle());

        // collect dependencies by scope
        Map<String, List<Artifact>> dependenciesByScope = dependencies.getDependenciesByScope(false);

        renderDependenciesForAllScopes(dependenciesByScope, false);

        endSection();
    }

    /**
     * @param dependenciesByScope map with supported scopes as key and a list of <code>Artifact</code> as values.
     * @param isTransitive <code>true</code> if it is transitive dependencies rendering.
     * @see Artifact#SCOPE_COMPILE
     * @see Artifact#SCOPE_PROVIDED
     * @see Artifact#SCOPE_RUNTIME
     * @see Artifact#SCOPE_SYSTEM
     * @see Artifact#SCOPE_TEST
     */
    private void renderDependenciesForAllScopes(Map<String, List<Artifact>> dependenciesByScope,
            boolean isTransitive) {
        renderDependenciesForScope(Artifact.SCOPE_COMPILE, dependenciesByScope.get(Artifact.SCOPE_COMPILE),
                isTransitive);
        renderDependenciesForScope(Artifact.SCOPE_RUNTIME, dependenciesByScope.get(Artifact.SCOPE_RUNTIME),
                isTransitive);
        renderDependenciesForScope(Artifact.SCOPE_TEST, dependenciesByScope.get(Artifact.SCOPE_TEST), isTransitive);
        renderDependenciesForScope(Artifact.SCOPE_PROVIDED, dependenciesByScope.get(Artifact.SCOPE_PROVIDED),
                isTransitive);
        renderDependenciesForScope(Artifact.SCOPE_SYSTEM, dependenciesByScope.get(Artifact.SCOPE_SYSTEM),
                isTransitive);
    }

    private void renderSectionProjectTransitiveDependencies() {
        Map<String, List<Artifact>> dependenciesByScope = dependencies.getDependenciesByScope(true);

        startSection(getI18nString("transitive.title"));

        if (dependenciesByScope.values().isEmpty()) {
            paragraph(getI18nString("transitive.nolist"));
        } else {
            paragraph(getI18nString("transitive.intro"));

            renderDependenciesForAllScopes(dependenciesByScope, true);
        }

        endSection();
    }

    private void renderSectionProjectDependencyGraph() {
        startSection(getI18nString("graph.title"));

        // === SubSection: Dependency Tree
        renderSectionDependencyTree();

        endSection();
    }

    private void renderSectionDependencyTree() {
        sink.rawText(JAVASCRIPT);

        // for Dependencies Graph Tree
        startSection(getI18nString("graph.tree.title"));

        sink.list();
        printDependencyListing(dependencyNode);
        sink.list_();

        endSection();
    }

    private void renderSectionDependencyFileDetails() {
        startSection(getI18nString("file.details.title"));

        List<Artifact> alldeps = dependencies.getAllDependencies();
        Collections.sort(alldeps, getArtifactComparator());

        // i18n
        String filename = getI18nString("file.details.column.file");
        String size = getI18nString("file.details.column.size");
        String entries = getI18nString("file.details.column.entries");
        String classes = getI18nString("file.details.column.classes");
        String packages = getI18nString("file.details.column.packages");
        String jdkrev = getI18nString("file.details.column.jdkrev");
        String debugInformation = getI18nString("file.details.column.debuginformation");
        String debugInformationTitle = getI18nString("file.details.columntitle.debuginformation");
        String debugInformationCellYes = getI18nString("file.details.cell.debuginformation.yes");
        String debugInformationCellNo = getI18nString("file.details.cell.debuginformation.no");
        String sealed = getI18nString("file.details.column.sealed");

        int[] justification = new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_RIGHT,
                Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_CENTER, Sink.JUSTIFY_CENTER,
                Sink.JUSTIFY_CENTER };

        startTable(justification, false);

        TotalCell totaldeps = new TotalCell(DEFAULT_DECIMAL_FORMAT);
        TotalCell totaldepsize = new TotalCell(fileLengthDecimalFormat);
        TotalCell totalentries = new TotalCell(DEFAULT_DECIMAL_FORMAT);
        TotalCell totalclasses = new TotalCell(DEFAULT_DECIMAL_FORMAT);
        TotalCell totalpackages = new TotalCell(DEFAULT_DECIMAL_FORMAT);
        double highestjdk = 0.0;
        TotalCell totalDebugInformation = new TotalCell(DEFAULT_DECIMAL_FORMAT);
        TotalCell totalsealed = new TotalCell(DEFAULT_DECIMAL_FORMAT);

        boolean hasSealed = hasSealed(alldeps);

        // Table header
        String[] tableHeader;
        String[] tableHeaderTitles;
        if (hasSealed) {
            tableHeader = new String[] { filename, size, entries, classes, packages, jdkrev, debugInformation,
                    sealed };
            tableHeaderTitles = new String[] { null, null, null, null, null, null, debugInformationTitle, null };
        } else {
            tableHeader = new String[] { filename, size, entries, classes, packages, jdkrev, debugInformation };
            tableHeaderTitles = new String[] { null, null, null, null, null, null, debugInformationTitle };
        }
        tableHeader(tableHeader, tableHeaderTitles);

        // Table rows
        for (Artifact artifact : alldeps) {
            if (artifact.getFile() == null) {
                log.error("Artifact: " + artifact.getId() + " has no file.");
                continue;
            }

            File artifactFile = artifact.getFile();

            totaldeps.incrementTotal(artifact.getScope());
            totaldepsize.addTotal(artifactFile.length(), artifact.getScope());

            if (JAR_SUBTYPE.contains(artifact.getType().toLowerCase())) {
                try {
                    JarData jarDetails = dependencies.getJarDependencyDetails(artifact);

                    String debugInformationCellValue = debugInformationCellNo;
                    if (jarDetails.isDebugPresent()) {
                        debugInformationCellValue = debugInformationCellYes;
                        totalDebugInformation.incrementTotal(artifact.getScope());
                    }

                    totalentries.addTotal(jarDetails.getNumEntries(), artifact.getScope());
                    totalclasses.addTotal(jarDetails.getNumClasses(), artifact.getScope());
                    totalpackages.addTotal(jarDetails.getNumPackages(), artifact.getScope());

                    try {
                        if (jarDetails.getJdkRevision() != null) {
                            highestjdk = Math.max(highestjdk, Double.parseDouble(jarDetails.getJdkRevision()));
                        }
                    } catch (NumberFormatException e) {
                        // ignore
                    }

                    String sealedstr = "";
                    if (jarDetails.isSealed()) {
                        sealedstr = "sealed";
                        totalsealed.incrementTotal(artifact.getScope());
                    }

                    String name = artifactFile.getName();
                    String fileLength = fileLengthDecimalFormat.format(artifactFile.length());

                    if (artifactFile.isDirectory()) {
                        File parent = artifactFile.getParentFile();
                        name = parent.getParentFile().getName() + '/' + parent.getName() + '/'
                                + artifactFile.getName();
                        fileLength = "-";
                    }

                    tableRow(hasSealed,
                            new String[] { name, fileLength,
                                    DEFAULT_DECIMAL_FORMAT.format(jarDetails.getNumEntries()),
                                    DEFAULT_DECIMAL_FORMAT.format(jarDetails.getNumClasses()),
                                    DEFAULT_DECIMAL_FORMAT.format(jarDetails.getNumPackages()),
                                    jarDetails.getJdkRevision(), debugInformationCellValue, sealedstr });
                } catch (IOException e) {
                    createExceptionInfoTableRow(artifact, artifactFile, e, hasSealed);
                }
            } else {
                tableRow(hasSealed, new String[] { artifactFile.getName(),
                        fileLengthDecimalFormat.format(artifactFile.length()), "", "", "", "", "", "" });
            }
        }

        // Total raws
        tableHeader[0] = getI18nString("file.details.total");
        tableHeader(tableHeader);

        justification[0] = Sink.JUSTIFY_RIGHT;
        justification[6] = Sink.JUSTIFY_RIGHT;

        for (int i = -1; i < TotalCell.SCOPES_COUNT; i++) {
            if (totaldeps.getTotal(i) > 0) {
                tableRow(hasSealed,
                        new String[] { totaldeps.getTotalString(i), totaldepsize.getTotalString(i),
                                totalentries.getTotalString(i), totalclasses.getTotalString(i),
                                totalpackages.getTotalString(i), (i < 0) ? String.valueOf(highestjdk) : "",
                                totalDebugInformation.getTotalString(i), totalsealed.getTotalString(i) });
            }
        }

        endTable();
        endSection();
    }

    // Almost as same as in the abstract class but includes the title attribute
    private void tableHeader(String[] content, String[] titles) {
        sink.tableRow();

        if (content != null) {
            if (titles != null && content.length != titles.length)
                throw new IllegalArgumentException(
                        "Length of title array must equal the length of the content array");

            for (int i = 0; i < content.length; i++) {
                if (titles != null)
                    tableHeaderCell(content[i], titles[i]);
                else
                    tableHeaderCell(content[i]);
            }
        }

        sink.tableRow_();
    }

    private void tableHeaderCell(String text, String title) {
        if (title != null) {
            SinkEventAttributes attributes = new SinkEventAttributeSet(SinkEventAttributes.TITLE, title);
            sink.tableHeaderCell(attributes);
        } else {
            sink.tableHeaderCell();
        }

        text(text);

        sink.tableHeaderCell_();
    }

    private void tableRow(boolean fullRow, String[] content) {
        sink.tableRow();

        int count = fullRow ? content.length : (content.length - 1);

        for (int i = 0; i < count; i++) {
            tableCell(content[i]);
        }

        sink.tableRow_();
    }

    private void createExceptionInfoTableRow(Artifact artifact, File artifactFile, Exception e, boolean hasSealed) {
        tableRow(hasSealed, new String[] { artifact.getId(), artifactFile.getAbsolutePath(), e.getMessage(), "", "",
                "", "", "" });
    }

    private void populateRepositoryMap(Map<String, ArtifactRepository> repos, List<ArtifactRepository> rowRepos) {
        for (ArtifactRepository repo : rowRepos) {
            repos.put(repo.getId(), repo);
        }
    }

    private void blacklistRepositoryMap(Map<String, ArtifactRepository> repos, List<String> repoUrlBlackListed) {
        for (ArtifactRepository repo : repos.values()) {
            // ping repo
            if (repo.isBlacklisted()) {
                repoUrlBlackListed.add(repo.getUrl());
            } else {
                if (repoUrlBlackListed.contains(repo.getUrl())) {
                    repo.setBlacklisted(true);
                } else {
                    try {
                        URL repoUrl = new URL(repo.getUrl());
                        if (ProjectInfoReportUtils.getContent(repoUrl, settings) == null) {
                            log.warn("The repository url '" + repoUrl + "' has no stream - Repository '"
                                    + repo.getId() + "' will be blacklisted.");
                            repo.setBlacklisted(true);
                            repoUrlBlackListed.add(repo.getUrl());
                        }
                    } catch (IOException e) {
                        log.warn("The repository url '" + repo.getUrl() + "' is invalid - Repository '"
                                + repo.getId() + "' will be blacklisted.");
                        repo.setBlacklisted(true);
                        repoUrlBlackListed.add(repo.getUrl());
                    }
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void renderSectionDependencyRepositoryLocations() {
        startSection(getI18nString("repo.locations.title"));

        // Collect Alphabetical Dependencies
        List<Artifact> alldeps = dependencies.getAllDependencies();
        Collections.sort(alldeps, getArtifactComparator());

        // Collect Repositories
        Map<String, ArtifactRepository> repoMap = new HashMap<String, ArtifactRepository>();

        populateRepositoryMap(repoMap, repoUtils.getRemoteArtifactRepositories());
        for (Artifact artifact : alldeps) {
            try {
                MavenProject artifactProject = repoUtils.getMavenProjectFromRepository(artifact);
                populateRepositoryMap(repoMap, artifactProject.getRemoteArtifactRepositories());
            } catch (ProjectBuildingException e) {
                log.warn("Unable to create Maven project from repository for artifact " + artifact.getId(), e);
            }
        }

        List<String> repoUrlBlackListed = new ArrayList<String>();
        blacklistRepositoryMap(repoMap, repoUrlBlackListed);

        // Render Repository List

        printRepositories(repoMap, repoUrlBlackListed);

        // Render Artifacts locations

        printArtifactsLocations(repoMap, repoUrlBlackListed, alldeps);

        endSection();
    }

    private void renderSectionDependencyLicenseListing() {
        startSection(getI18nString("graph.tables.licenses"));
        printGroupedLicenses();
        endSection();
    }

    private void renderDependenciesForScope(String scope, List<Artifact> artifacts, boolean isTransitive) {
        if (artifacts != null) {
            boolean withClassifier = hasClassifier(artifacts);
            boolean withOptional = hasOptional(artifacts);
            String[] tableHeader = getDependencyTableHeader(withClassifier, withOptional);

            // can't use straight artifact comparison because we want optional last
            Collections.sort(artifacts, getArtifactComparator());

            String anchorByScope = (isTransitive ? getI18nString("transitive.title") + "_" + scope
                    : getI18nString("title") + "_" + scope);
            startSection(anchorByScope, scope);

            paragraph(getI18nString("intro." + scope));

            startTable();
            tableHeader(tableHeader);
            for (Artifact artifact : artifacts) {
                renderArtifactRow(artifact, withClassifier, withOptional);
            }
            endTable();

            endSection();
        }
    }

    private Comparator<Artifact> getArtifactComparator() {
        return new Comparator<Artifact>() {
            public int compare(Artifact a1, Artifact a2) {
                // put optional last
                if (a1.isOptional() && !a2.isOptional()) {
                    return +1;
                } else if (!a1.isOptional() && a2.isOptional()) {
                    return -1;
                } else {
                    return a1.compareTo(a2);
                }
            }
        };
    }

    /**
     * @param artifact not null
     * @param withClassifier <code>true</code> to include the classifier column, <code>false</code> otherwise.
     * @param withOptional <code>true</code> to include the optional column, <code>false</code> otherwise.
     * @see #getDependencyTableHeader(boolean, boolean)
     */
    private void renderArtifactRow(Artifact artifact, boolean withClassifier, boolean withOptional) {
        String isOptional = artifact.isOptional() ? getI18nString("column.isOptional")
                : getI18nString("column.isNotOptional");

        String url = ProjectInfoReportUtils.getArtifactUrl(artifactFactory, artifact, mavenProjectBuilder,
                remoteRepositories, localRepository);
        String artifactIdCell = ProjectInfoReportUtils.getArtifactIdCell(artifact.getArtifactId(), url);

        MavenProject artifactProject;
        StringBuilder sb = new StringBuilder();
        try {
            artifactProject = repoUtils.getMavenProjectFromRepository(artifact);
            @SuppressWarnings("unchecked")
            List<License> licenses = artifactProject.getLicenses();
            for (License license : licenses) {
                sb.append(ProjectInfoReportUtils.getArtifactIdCell(license.getName(), license.getUrl()));
            }
        } catch (ProjectBuildingException e) {
            log.warn("Unable to create Maven project from repository.", e);
        }

        String content[];
        if (withClassifier) {
            content = new String[] { artifact.getGroupId(), artifactIdCell, artifact.getVersion(),
                    artifact.getClassifier(), artifact.getType(), sb.toString(), isOptional };
        } else {
            content = new String[] { artifact.getGroupId(), artifactIdCell, artifact.getVersion(),
                    artifact.getType(), sb.toString(), isOptional };
        }

        tableRow(withOptional, content);
    }

    private void printDependencyListing(DependencyNode node) {
        Artifact artifact = node.getArtifact();
        String id = artifact.getId();
        String dependencyDetailId = "_dep" + idCounter++;
        String imgId = "_img" + idCounter++;

        sink.listItem();

        sink.text(id + (StringUtils.isNotEmpty(artifact.getScope()) ? " (" + artifact.getScope() + ") " : " "));
        sink.rawText("<img id=\"" + imgId + "\" src=\"" + IMG_INFO_URL
                + "\" alt=\"Information\" onclick=\"toggleDependencyDetail( '" + dependencyDetailId + "', '" + imgId
                + "' );\" style=\"cursor: pointer;vertical-align:text-bottom;\"></img>");

        printDescriptionsAndURLs(node, dependencyDetailId);

        if (!node.getChildren().isEmpty()) {
            boolean toBeIncluded = false;
            List<DependencyNode> subList = new ArrayList<DependencyNode>();
            for (DependencyNode dep : node.getChildren()) {
                if (dependencies.getAllDependencies().contains(dep.getArtifact())) {
                    subList.add(dep);
                    toBeIncluded = true;
                }
            }

            if (toBeIncluded) {
                sink.list();
                for (DependencyNode dep : subList) {
                    printDependencyListing(dep);
                }
                sink.list_();
            }
        }

        sink.listItem_();
    }

    private void printDescriptionsAndURLs(DependencyNode node, String uid) {
        Artifact artifact = node.getArtifact();
        String id = artifact.getId();
        String unknownLicenseMessage = getI18nString("graph.tables.unknown");

        sink.rawText("<div id=\"" + uid + "\" style=\"display:none\">");

        sink.table();

        if (!Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
            try {
                MavenProject artifactProject = repoUtils.getMavenProjectFromRepository(artifact);
                String artifactDescription = artifactProject.getDescription();
                String artifactUrl = artifactProject.getUrl();
                String artifactName = artifactProject.getName();
                @SuppressWarnings("unchecked")
                List<License> licenses = artifactProject.getLicenses();

                sink.tableRow();
                sink.tableHeaderCell();
                sink.text(artifactName);
                sink.tableHeaderCell_();
                sink.tableRow_();

                sink.tableRow();
                sink.tableCell();

                sink.paragraph();
                sink.bold();
                sink.text(getI18nString("column.description") + ": ");
                sink.bold_();
                if (StringUtils.isNotEmpty(artifactDescription)) {
                    sink.text(artifactDescription);
                } else {
                    sink.text(getI18nString("index", "nodescription"));
                }
                sink.paragraph_();

                if (StringUtils.isNotEmpty(artifactUrl)) {
                    sink.paragraph();
                    sink.bold();
                    sink.text(getI18nString("column.url") + ": ");
                    sink.bold_();
                    if (ProjectInfoReportUtils.isArtifactUrlValid(artifactUrl)) {
                        sink.link(artifactUrl);
                        sink.text(artifactUrl);
                        sink.link_();
                    } else {
                        sink.text(artifactUrl);
                    }
                    sink.paragraph_();
                }

                sink.paragraph();
                sink.bold();
                sink.text(getI18nString("license", "title") + ": ");
                sink.bold_();
                if (!licenses.isEmpty()) {
                    for (License element : licenses) {
                        String licenseName = element.getName();
                        String licenseUrl = element.getUrl();

                        if (licenseUrl != null) {
                            sink.link(licenseUrl);
                        }
                        sink.text(licenseName);

                        if (licenseUrl != null) {
                            sink.link_();
                        }

                        licenseMap.put(licenseName, artifactName);
                    }
                } else {
                    sink.text(getI18nString("license", "nolicense"));

                    licenseMap.put(unknownLicenseMessage, artifactName);
                }
                sink.paragraph_();
            } catch (ProjectBuildingException e) {
                log.warn("Unable to create Maven project from repository for artifact " + artifact.getId(), e);
            }
        } else {
            sink.tableRow();
            sink.tableHeaderCell();
            sink.text(id);
            sink.tableHeaderCell_();
            sink.tableRow_();

            sink.tableRow();
            sink.tableCell();

            sink.paragraph();
            sink.bold();
            sink.text(getI18nString("column.description") + ": ");
            sink.bold_();
            sink.text(getI18nString("index", "nodescription"));
            sink.paragraph_();

            if (artifact.getFile() != null) {
                sink.paragraph();
                sink.bold();
                sink.text(getI18nString("column.url") + ": ");
                sink.bold_();
                sink.text(artifact.getFile().getAbsolutePath());
                sink.paragraph_();
            }
        }

        sink.tableCell_();
        sink.tableRow_();

        sink.table_();

        sink.rawText("</div>");
    }

    private void printGroupedLicenses() {
        for (Map.Entry<String, Object> entry : licenseMap.entrySet()) {
            String licenseName = entry.getKey();
            sink.paragraph();
            sink.bold();
            if (StringUtils.isEmpty(licenseName)) {
                sink.text(getI18nString("unamed"));
            } else {
                sink.text(licenseName);
            }
            sink.text(": ");
            sink.bold_();

            @SuppressWarnings("unchecked")
            SortedSet<String> projects = (SortedSet<String>) entry.getValue();

            for (Iterator<String> iterator = projects.iterator(); iterator.hasNext();) {
                String projectName = iterator.next();
                sink.text(projectName);
                if (iterator.hasNext()) {
                    sink.text(", ");
                }
            }

            sink.paragraph_();
        }
    }

    private void printRepositories(Map<String, ArtifactRepository> repoMap, List<String> repoUrlBlackListed) {
        // i18n
        String repoid = getI18nString("repo.locations.column.repoid");
        String url = getI18nString("repo.locations.column.url");
        String release = getI18nString("repo.locations.column.release");
        String snapshot = getI18nString("repo.locations.column.snapshot");
        String blacklisted = getI18nString("repo.locations.column.blacklisted");
        String releaseEnabled = getI18nString("repo.locations.cell.release.enabled");
        String releaseDisabled = getI18nString("repo.locations.cell.release.disabled");
        String snapshotEnabled = getI18nString("repo.locations.cell.snapshot.enabled");
        String snapshotDisabled = getI18nString("repo.locations.cell.snapshot.disabled");
        String blacklistedEnabled = getI18nString("repo.locations.cell.blacklisted.enabled");
        String blacklistedDisabled = getI18nString("repo.locations.cell.blacklisted.disabled");

        // Table header

        String[] tableHeader;
        int[] justificationRepo;
        if (repoUrlBlackListed.isEmpty()) {
            tableHeader = new String[] { repoid, url, release, snapshot };
            justificationRepo = new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_LEFT, Sink.JUSTIFY_CENTER,
                    Sink.JUSTIFY_CENTER };
        } else {
            tableHeader = new String[] { repoid, url, release, snapshot, blacklisted };
            justificationRepo = new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_LEFT, Sink.JUSTIFY_CENTER,
                    Sink.JUSTIFY_CENTER, Sink.JUSTIFY_CENTER };
        }

        startTable(justificationRepo, false);

        tableHeader(tableHeader);

        // Table rows

        for (ArtifactRepository repo : repoMap.values()) {
            List<ArtifactRepository> mirroredRepos = getMirroredRepositories(repo);

            sink.tableRow();
            sink.tableCell();
            boolean addLineBreak = false;
            for (ArtifactRepository r : mirroredRepos) {
                if (addLineBreak) {
                    sink.lineBreak();
                }
                addLineBreak = true;
                sink.text(r.getId());
            }
            sink.tableCell_();

            sink.tableCell();
            addLineBreak = false;
            for (ArtifactRepository r : mirroredRepos) {
                if (addLineBreak) {
                    sink.lineBreak();
                }
                addLineBreak = true;
                if (repo.isBlacklisted()) {
                    sink.text(r.getUrl());
                } else {
                    sink.link(r.getUrl());
                    sink.text(r.getUrl());
                    sink.link_();
                }
            }
            sink.tableCell_();

            ArtifactRepositoryPolicy releasePolicy = repo.getReleases();
            tableCell(releasePolicy.isEnabled() ? releaseEnabled : releaseDisabled);

            ArtifactRepositoryPolicy snapshotPolicy = repo.getSnapshots();
            tableCell(snapshotPolicy.isEnabled() ? snapshotEnabled : snapshotDisabled);

            if (!repoUrlBlackListed.isEmpty()) {
                tableCell(repoUrlBlackListed.contains(repo.getUrl()) ? blacklistedEnabled : blacklistedDisabled);
            }

            sink.tableRow_();
        }

        endTable();
    }

    private Object invoke(Object object, String method)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        return object.getClass().getMethod(method).invoke(object);
    }

    /**
     * Get the repos that can be hidden behind a mirror.
     *
     * @param repo the repo used to download artifacts
     * @return the mirrored repositories or a singleton with actual repo if it is not a mirror
     */
    private List<ArtifactRepository> getMirroredRepositories(ArtifactRepository repo) {
        try {
            @SuppressWarnings("unchecked")
            List<ArtifactRepository> mirroredRepos = (List<ArtifactRepository>) invoke(repo,
                    "getMirroredRepositories");

            if ((mirroredRepos != null) && (!mirroredRepos.isEmpty())) {
                return mirroredRepos;
            }
        } catch (IllegalArgumentException e) {
            // ignore: API not available before Maven 3.0.3
        } catch (SecurityException e) {
            // ignore: API not available before Maven 3.0.3
        } catch (IllegalAccessException e) {
            // ignore: API not available before Maven 3.0.3
        } catch (InvocationTargetException e) {
            // ignore: API not available before Maven 3.0.3
        } catch (NoSuchMethodException e) {
            // ignore: API not available before Maven 3.0.3
        }
        // before Maven 3.0.3, we can't do anything: Maven 3.0-alpha to 3.0.2 will show the mirror
        return Collections.singletonList(repo);
    }

    private void printArtifactsLocations(Map<String, ArtifactRepository> repoMap, List<String> repoUrlBlackListed,
            List<Artifact> alldeps) {
        // i18n
        String artifact = getI18nString("repo.locations.column.artifact");

        sink.paragraph();
        sink.text(getI18nString("repo.locations.artifact.breakdown"));
        sink.paragraph_();

        List<String> repoIdList = new ArrayList<String>();
        // removed blacklisted repo
        for (Map.Entry<String, ArtifactRepository> entry : repoMap.entrySet()) {
            String repokey = entry.getKey();
            ArtifactRepository repo = entry.getValue();
            if (!(repo.isBlacklisted() || repoUrlBlackListed.contains(repo.getUrl()))) {
                repoIdList.add(repokey);
            }
        }

        String[] tableHeader = new String[repoIdList.size() + 1];
        int[] justificationRepo = new int[repoIdList.size() + 1];

        tableHeader[0] = artifact;
        justificationRepo[0] = Sink.JUSTIFY_LEFT;

        int idnum = 1;
        for (String id : repoIdList) {
            tableHeader[idnum] = id;
            justificationRepo[idnum] = Sink.JUSTIFY_CENTER;
            idnum++;
        }

        Map<String, Integer> totalByRepo = new HashMap<String, Integer>();
        TotalCell totaldeps = new TotalCell(DEFAULT_DECIMAL_FORMAT);

        startTable(justificationRepo, false);

        tableHeader(tableHeader);

        for (Artifact dependency : alldeps) {
            totaldeps.incrementTotal(dependency.getScope());

            sink.tableRow();

            tableCell(dependency.getId());

            if (Artifact.SCOPE_SYSTEM.equals(dependency.getScope())) {
                for (@SuppressWarnings("unused")
                String repoId : repoIdList) {
                    tableCell("-");
                }
            } else {
                for (String repokey : repoIdList) {
                    ArtifactRepository repo = repoMap.get(repokey);

                    String depUrl = repoUtils.getDependencyUrlFromRepository(dependency, repo);

                    Integer old = totalByRepo.get(repokey);
                    if (old == null) {
                        old = 0;
                        totalByRepo.put(repokey, old);
                    }

                    boolean dependencyExists = false;
                    // check snapshots in snapshots repository only and releases in release repositories...
                    if ((dependency.isSnapshot() && repo.getSnapshots().isEnabled())
                            || (!dependency.isSnapshot() && repo.getReleases().isEnabled())) {
                        dependencyExists = repoUtils.dependencyExistsInRepo(repo, dependency);
                    }

                    if (dependencyExists) {
                        sink.tableCell();
                        if (StringUtils.isNotEmpty(depUrl)) {
                            sink.link(depUrl);
                        } else {
                            sink.text(depUrl);
                        }

                        sink.figure();
                        sink.figureCaption();
                        sink.text("Found at " + repo.getUrl());
                        sink.figureCaption_();
                        sink.figureGraphics("images/icon_success_sml.gif");
                        sink.figure_();

                        sink.link_();
                        sink.tableCell_();

                        totalByRepo.put(repokey, old.intValue() + 1);
                    } else {
                        tableCell("-");
                    }
                }
            }

            sink.tableRow_();
        }

        // Total row

        // reused key
        tableHeader[0] = getI18nString("file.details.total");
        tableHeader(tableHeader);
        String[] totalRow = new String[repoIdList.size() + 1];
        totalRow[0] = totaldeps.toString();
        idnum = 1;
        for (String repokey : repoIdList) {
            Integer deps = totalByRepo.get(repokey);
            totalRow[idnum++] = deps != null ? deps.toString() : "0";
        }

        tableRow(totalRow);

        endTable();
    }

    /**
     * @param artifacts not null
     * @return <code>true</code> if one artifact in the list has a classifier, <code>false</code> otherwise.
     */
    private boolean hasClassifier(List<Artifact> artifacts) {
        for (Artifact artifact : artifacts) {
            if (StringUtils.isNotEmpty(artifact.getClassifier())) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param artifacts not null
     * @return <code>true</code> if one artifact in the list is optional, <code>false</code> otherwise.
     */
    private boolean hasOptional(List<Artifact> artifacts) {
        for (Artifact artifact : artifacts) {
            if (artifact.isOptional()) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param artifacts not null
     * @return <code>true</code> if one artifact in the list is sealed, <code>false</code> otherwise.
     */
    private boolean hasSealed(List<Artifact> artifacts) {
        for (Artifact artifact : artifacts) {
            // TODO site:run Why do we need to resolve this...
            if (artifact.getFile() == null) {
                if (Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
                    // can not resolve system scope artifact file
                    continue;
                }

                try {
                    repoUtils.resolve(artifact);
                } catch (ArtifactResolutionException e) {
                    log.error("Artifact: " + artifact.getId() + " has no file.", e);
                    continue;
                } catch (ArtifactNotFoundException e) {
                    if ((dependencies.getProject().getGroupId().equals(artifact.getGroupId()))
                            && (dependencies.getProject().getArtifactId().equals(artifact.getArtifactId()))
                            && (dependencies.getProject().getVersion().equals(artifact.getVersion()))) {
                        log.warn("The artifact of this project has never been deployed.");
                    } else {
                        log.error("Artifact: " + artifact.getId() + " has no file.", e);
                    }

                    continue;
                }

                if (artifact.getFile() == null) {
                    log.error("Artifact: " + artifact.getId() + " has no file, even after resolution.");
                    continue;
                }
            }

            if (JAR_SUBTYPE.contains(artifact.getType().toLowerCase())) {
                try {
                    JarData jarDetails = dependencies.getJarDependencyDetails(artifact);
                    if (jarDetails.isSealed()) {
                        return true;
                    }
                } catch (IOException e) {
                    log.error("Artifact: " + artifact.getId() + " caused IOException: " + e.getMessage(), e);
                }
            }
        }
        return false;
    }

    /**
     * Formats file length with the associated <a href="https://en.wikipedia.org/wiki/Metric_prefix">SI</a> prefix
     * (GB, MB, kB) and using the pattern <code>###0.00</code> by default.
     *
     * @see <a href="https://en.wikipedia.org/wiki/Metric_prefix">https://en.wikipedia.org/wiki/Metric_prefix</a>
     * @see <a href="https://en.wikipedia.org/wiki/Binary_prefix">https://en.wikipedia.org/wiki/Binary_prefix</a>
     * @see <a href="https://en.wikipedia.org/wiki/Octet_%28computing%29">https://en.wikipedia.org/wiki/Octet_(computing)</a>
     */
    static class FileDecimalFormat extends DecimalFormat {
        private static final long serialVersionUID = 4062503546523610081L;

        private final I18N i18n;

        private final Locale locale;

        /**
         * Default constructor
         *
         * @param i18n
         * @param locale
         */
        public FileDecimalFormat(I18N i18n, Locale locale) {
            super("###0.00");

            this.i18n = i18n;
            this.locale = locale;
        }

        /** {@inheritDoc} */
        public StringBuffer format(long fs, StringBuffer result, FieldPosition fieldPosition) {
            if (fs > 1000 * 1000 * 1000) {
                result = super.format((float) fs / (1000 * 1000 * 1000), result, fieldPosition);
                result.append(" ").append(getString("report.dependencies.file.details.column.size.gb"));
                return result;
            }

            if (fs > 1000 * 1000) {
                result = super.format((float) fs / (1000 * 1000), result, fieldPosition);
                result.append(" ").append(getString("report.dependencies.file.details.column.size.mb"));
                return result;
            }

            result = super.format((float) fs / (1000), result, fieldPosition);
            result.append(" ").append(getString("report.dependencies.file.details.column.size.kb"));
            return result;
        }

        private String getString(String key) {
            return i18n.getString("project-info-report", locale, key);
        }
    }

    /**
     * Combine total and total by scope in a cell.
     */
    static class TotalCell {
        static final int SCOPES_COUNT = 5;

        final DecimalFormat decimalFormat;

        long total = 0;

        long totalCompileScope = 0;

        long totalTestScope = 0;

        long totalRuntimeScope = 0;

        long totalProvidedScope = 0;

        long totalSystemScope = 0;

        TotalCell(DecimalFormat decimalFormat) {
            this.decimalFormat = decimalFormat;
        }

        void incrementTotal(String scope) {
            addTotal(1, scope);
        }

        static String getScope(int index) {
            switch (index) {
            case 0:
                return Artifact.SCOPE_COMPILE;
            case 1:
                return Artifact.SCOPE_TEST;
            case 2:
                return Artifact.SCOPE_RUNTIME;
            case 3:
                return Artifact.SCOPE_PROVIDED;
            case 4:
                return Artifact.SCOPE_SYSTEM;
            default:
                return null;
            }
        }

        long getTotal(int index) {
            switch (index) {
            case 0:
                return totalCompileScope;
            case 1:
                return totalTestScope;
            case 2:
                return totalRuntimeScope;
            case 3:
                return totalProvidedScope;
            case 4:
                return totalSystemScope;
            default:
                return total;
            }
        }

        String getTotalString(int index) {
            long totalString = getTotal(index);

            if (totalString <= 0) {
                return "";
            }

            StringBuilder sb = new StringBuilder();
            if (index >= 0) {
                sb.append(getScope(index)).append(": ");
            }
            sb.append(decimalFormat.format(getTotal(index)));
            return sb.toString();
        }

        void addTotal(long add, String scope) {
            total += add;

            if (Artifact.SCOPE_COMPILE.equals(scope)) {
                totalCompileScope += add;
            } else if (Artifact.SCOPE_TEST.equals(scope)) {
                totalTestScope += add;
            } else if (Artifact.SCOPE_RUNTIME.equals(scope)) {
                totalRuntimeScope += add;
            } else if (Artifact.SCOPE_PROVIDED.equals(scope)) {
                totalProvidedScope += add;
            } else if (Artifact.SCOPE_SYSTEM.equals(scope)) {
                totalSystemScope += add;
            }
        }

        /** {@inheritDoc} */
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(decimalFormat.format(total));
            sb.append(" (");

            boolean needSeparator = false;
            for (int i = 0; i < SCOPES_COUNT; i++) {
                if (getTotal(i) > 0) {
                    if (needSeparator) {
                        sb.append(", ");
                    }
                    sb.append(getTotalString(i));
                    needSeparator = true;
                }
            }

            sb.append(")");

            return sb.toString();
        }
    }
}