DependencyReporter.java

/*
 *  Copyright (c) 2001-2025, Jean Tessier
 *  All rights reserved.
 *  
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *  
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *  
 *      * Neither the name of Jean Tessier nor the names of his contributors
 *        may be used to endorse or promote products derived from this software
 *        without specific prior written permission.
 *  
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
 *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jeantessier.dependencyfinder.cli;

import java.util.*;
import java.io.*;

import javax.xml.parsers.*;

import org.xml.sax.*;

import com.jeantessier.commandline.*;
import com.jeantessier.dependency.*;
import com.jeantessier.dependency.Printer;
import com.jeantessier.dependency.TextPrinter;
import com.jeantessier.dependency.Visitor;

public class DependencyReporter extends DependencyGraphCommand {
    protected void populateCommandLineSwitches() {
        super.populateCommandLineSwitches();
        populateCommandLineSwitchesForXMLOutput(XMLPrinter.DEFAULT_ENCODING, XMLPrinter.DEFAULT_DTD_PREFIX, XMLPrinter.DEFAULT_INDENT_TEXT);

        populateCommandLineSwitchesForScoping();
        populateCommandLineSwitchesForFiltering();

        getCommandLine().addAliasSwitch("p2p", "package-scope", "package-filter");
        getCommandLine().addAliasSwitch("c2p", "class-scope", "package-filter");
        getCommandLine().addAliasSwitch("c2c", "class-scope", "class-filter");
        getCommandLine().addAliasSwitch("f2f", "feature-scope", "feature-filter");
        getCommandLine().addAliasSwitch("includes", "scope-includes", "filter-includes");
        getCommandLine().addAliasSwitch("excludes", "scope-excludes", "filter-excludes");

        getCommandLine().addToggleSwitch("show-inbounds");
        getCommandLine().addToggleSwitch("show-outbounds");
        getCommandLine().addToggleSwitch("show-empty-nodes");

        getCommandLine().addToggleSwitch("html");
        getCommandLine().addToggleSwitch("json");
        getCommandLine().addToggleSwitch("text");
        getCommandLine().addToggleSwitch("txt");
        getCommandLine().addToggleSwitch("xml");
        getCommandLine().addToggleSwitch("yaml");
        getCommandLine().addToggleSwitch("yml");

        getCommandLine().addToggleSwitch("minimize");
        getCommandLine().addToggleSwitch("maximize");
        getCommandLine().addToggleSwitch("copy-only");
        getCommandLine().addToggleSwitch("include-filter-nodes");

        getCommandLine().addSingleValueSwitch("url-format", HTMLPrinter.DEFAULT_URL_FORMAT);
    }

    protected Collection<CommandLineException> parseCommandLine(String[] args) {
        Collection<CommandLineException> exceptions = super.parseCommandLine(args);

        exceptions.addAll(validateCommandLineForScoping());
        exceptions.addAll(validateCommandLineForFiltering());

        if (getCommandLine().getToggleSwitch("maximize") && getCommandLine().getToggleSwitch("minimize")) {
            exceptions.add(new CommandLineException("Only one of -maximize or -minimize is allowed"));
        }

        int modeSwitch = 0;

        if (getCommandLine().getToggleSwitch("html")) {
            modeSwitch++;
        }
        if (getCommandLine().getToggleSwitch("json")) {
            modeSwitch++;
        }
        if (getCommandLine().getToggleSwitch("text")) {
            modeSwitch++;
        }
        if (getCommandLine().getToggleSwitch("txt")) {
            modeSwitch++;
        }
        if (getCommandLine().getToggleSwitch("xml")) {
            modeSwitch++;
        }
        if (getCommandLine().getToggleSwitch("yaml")) {
            modeSwitch++;
        }
        if (getCommandLine().getToggleSwitch("yml")) {
            modeSwitch++;
        }
        if (modeSwitch > 1) {
            exceptions.add(new CommandLineException("Must have at most one of -html, -json, -text, -txt, -xml, -yml, or -yaml"));
        }

        return exceptions;
    }

    protected void doProcessing() throws Exception {
        SelectionCriteria scopeCriteria = getScopeCriteria();
        SelectionCriteria filterCriteria = getFilterCriteria();

        GraphCopier copier;
        if (getCommandLine().getToggleSwitch("copy-only") || getCommandLine().getToggleSwitch("maximize")) {
            copier = new GraphCopier(new SelectiveTraversalStrategy(scopeCriteria, filterCriteria));
        } else {
            copier = new GraphSummarizer(scopeCriteria, filterCriteria);
        }
        copyGraph(copier);

        getVerboseListener().print("Printing the graph ...");

        Printer printer;
        if (getCommandLine().isPresent("html")) {
            printer = new HTMLPrinter(getOut(), getCommandLine().getSingleSwitch("url-format"));
        } else if (getCommandLine().isPresent("json")) {
            printer = new JSONPrinter(getOut());
        } else if (getCommandLine().isPresent("xml")) {
            printer = new XMLPrinter(getOut(), getCommandLine().getSingleSwitch("encoding"), getCommandLine().getSingleSwitch("dtd-prefix"));
        } else if (getCommandLine().isPresent("yaml") || getCommandLine().isPresent("yml")) {
            printer = new YAMLPrinter(getOut());
        } else {
            printer = new TextPrinter(getOut());
        }

        if (getCommandLine().isPresent("indent-text")) {
            printer.setIndentText(getCommandLine().getSingleSwitch("indent-text"));
        }

        if (getCommandLine().isPresent("show-inbounds") || getCommandLine().isPresent("show-outbounds") || getCommandLine().isPresent("show-empty-nodes")) {
            printer.setShowInbounds(getCommandLine().isPresent("show-inbounds"));
            printer.setShowOutbounds(getCommandLine().isPresent("show-outbounds"));
            printer.setShowEmptyNodes(getCommandLine().isPresent("show-empty-nodes"));
        }

        var packages = copier.getScopeFactory().getPackages().values();

        if (getCommandLine().isPresent("include-filter-nodes")) {
            var nodeFactory = new NodeFactory();
            new GraphCopier(nodeFactory).traverseNodes(packages);
            packages = nodeFactory.getPackages().values();
        }

        printer.traverseNodes(packages);
    }

    private void copyGraph(Visitor copier) throws IOException, SAXException, ParserConfigurationException {
        if (getCommandLine().getParameters().isEmpty()) {
            copyGraphFromSystemIn(copier);
        } else {
            copyGraphFromFiles(copier);
        }
    }

    private void copyGraphFromSystemIn(Visitor copier) throws IOException, ParserConfigurationException, SAXException {
        copyGraph(copier, loadGraphFromSystemIn());
    }

    private void copyGraphFromFiles(Visitor copier) throws IOException, SAXException, ParserConfigurationException {
        for (String filename : getCommandLine().getParameters()) {
            if (filename.endsWith(".xml")) {
                copyGraph(copier, loadGraphFromFile(filename));
            } else {
                getVerboseListener().print("Skipping \"" + filename + "\".");
            }

        }
    }

    private void copyGraph(Visitor copier, Collection<PackageNode> packages) {
        if (getCommandLine().getToggleSwitch("maximize")) {
            new LinkMaximizer().traverseNodes(packages);
        } else if (getCommandLine().getToggleSwitch("minimize")) {
            new LinkMinimizer().traverseNodes(packages);
        }

        copier.traverseNodes(packages);
    }

    private Collection<PackageNode> loadGraphFromSystemIn() throws IOException, SAXException, ParserConfigurationException {
        Collection<PackageNode> packages;

        getVerboseListener().print("Reading from standard input");

        NodeLoader loader = new NodeLoader(getCommandLine().getToggleSwitch("validate"));
        loader.addDependencyListener(getVerboseListener());
        packages = loader.load(System.in).getPackages().values();

        getVerboseListener().print("Read from standard input.");

        return packages;
    }

    private Collection<PackageNode> loadGraphFromFile(String filename) throws IOException, SAXException, ParserConfigurationException {
        Collection<PackageNode> packages;

        getVerboseListener().print("Reading " + filename);

        NodeLoader loader = new NodeLoader(getCommandLine().getToggleSwitch("validate"));
        loader.addDependencyListener(getVerboseListener());
        packages = loader.load(filename).getPackages().values();

        getVerboseListener().print("Read \"" + filename + "\".");

        return packages;
    }

    public static void main(String[] args) throws Exception {
        new DependencyReporter().run(args);
    }
}