Reporter.java

/*
 * 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.                                           *
 */
package org.apache.rat;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;

import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.function.IOSupplier;
import org.apache.rat.api.RatException;
import org.apache.rat.document.DocumentName;
import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
import org.apache.rat.report.RatReport;
import org.apache.rat.report.claim.ClaimStatistic;
import org.apache.rat.report.xml.XmlReportFactory;
import org.apache.rat.report.xml.writer.XmlWriter;
import org.apache.rat.utils.StandardXmlFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Class that executes the report as defined in a {@link ReportConfiguration} and stores
 * the result for later handling.
 */
public class Reporter {

    /**
     * Format used for listing licenses.
     */
    private static final String LICENSE_FORMAT = "%s:\t%s%n\t\t%s%n";

    /**
     * The configuration for the report.
     */
    private final ReportConfiguration configuration;

    /**
     * The output from the execution.
     */
    private Output output;

    /**
     * Create the reporter.
     *
     * @param configuration the configuration to use.
     */
    public Reporter(final ReportConfiguration configuration) {
        this.configuration = configuration;
    }

    /**
     * Executes the report and builds the output.
     *
     * @return the Output object.
     * @throws RatException on error.
     */
    public Output execute() throws RatException {
        try {
            Output.Builder builder = Output.builder().configuration(configuration);
            if (configuration.hasSource()) {
                StringBuilder sb = new StringBuilder();
                try (XmlWriter writer = new XmlWriter(sb)) {
                    writer.startDocument();
                    ClaimStatistic statistic = new ClaimStatistic();
                    builder.statistic(statistic);
                    RatReport report = XmlReportFactory.createStandardReport(writer, statistic, configuration);
                    report.startReport();
                    configuration.getSources().build().run(report);
                    report.endReport();
                    InputSource inputSource = new InputSource(new StringReader(sb.toString()));
                    builder.document(StandardXmlFactory.documentBuilder().parse(inputSource));
                }
            } else {
                builder.document = StandardXmlFactory.documentBuilder().newDocument();
                builder.statistic(new ClaimStatistic());
            }
            this.output = builder.build();
            return output;
        } catch (Exception e) {
            throw RatException.makeRatException(e);
        }
    }

    /**
     * Gets the output from the last {@link #execute} call or {@code null} if {@link #execute} has not been called.
     *
     * @return the output
     */
    public Output getOutput() {
        return output;
    }

    /**
     * The output from a report run.
     */
    public static final class Output {
        /**
         * The XML output document.
         */
        private final Document document;
        /**
         * The claim statics from the execution that generated the document.
         * May be empty if the Document was read from disk.
         */
        private final ClaimStatistic statistic;
        /**
         * The configuration that generated the document.
         */
        private final ReportConfiguration configuration;

        /**
         * Create an output with statistics.
         *
         * @param builder the Builder
         */
        private Output(final Builder builder) {
            this.document = builder.document;
            this.statistic = builder.statistic == null ? new ClaimStatistic() : builder.statistic;
            this.configuration = builder.configuration == null ? new ReportConfiguration() : builder.configuration;
        }

        public static Builder builder() {
            return new Builder();
        }

        /**
         * Gets the document that was generated during execution.
         *
         * @return the document that was generated during execution.
         */
        public Document getDocument() {
            return document;
        }

        /**
         * Get the claim statistics from the run.
         *
         * @return the claim statistics.
         */
        public ClaimStatistic getStatistic() {
            return statistic;
        }

        public ReportConfiguration getConfiguration() {
            return configuration;
        }

        /**
         * Formats the report to the output and using the stylesheet found in the report configuration.
         *
         * @param config the RAT report configuration.
         * @throws RatException on error.
         */
        public void format(final ReportConfiguration config) throws RatException {
            format(config.getStyleSheet(), config.getOutput());
        }

        /**
         * Formats the report to the specified output using the stylesheet. It is safe to call this method more than once
         * in order to generate multiple reports from the same run.
         *
         * @param stylesheet the style sheet to use for XSLT formatting.
         * @param output the output stream to write to.
         * @throws RatException on error.
         */
        public void format(final IOSupplier<InputStream> stylesheet, final IOSupplier<OutputStream> output) throws RatException {
            try (OutputStream out = output.get();
                 InputStream styleIn = stylesheet.get()) {
                StandardXmlFactory.createTransformer(styleIn).transform(new DOMSource(document),
                        new StreamResult(new OutputStreamWriter(out, StandardCharsets.UTF_8)));
            } catch (TransformerException | IOException e) {
                throw new RatException(e);
            }
        }

        /**
         * Lists the licenses on the print writer.
         *
         * @param printWriter the print writer to write to.
         * @param filter the license filter that specifies which licenses to output.
         */
        public void listLicenses(final PrintWriter printWriter, final LicenseFilter filter) {
            printWriter.format("Licenses (%s):%n", filter);
            configuration.getLicenses(filter)
                    .forEach(lic -> printWriter.format(LICENSE_FORMAT, lic.getLicenseFamily().getFamilyCategory(),
                            lic.getLicenseFamily().getFamilyName(), lic.getNote()));
            printWriter.println();
        }

        /**
         * Lists the licenses on the output specified in the configuration.
         *
         * @param filter the license filter that specifies which licenses to output.
         * @throws IOException if PrintWriter can not be retrieved from configuration.
         */
        public void listLicenses(final LicenseFilter filter) throws IOException {
            try (PrintWriter pw = configuration.getWriter().get()) {
                listLicenses(pw, filter);
            }
        }

        /**
         * Writes a text summary of issues with the run.
         *
         * @param appendable the appendable to write to.
         * @throws IOException on error.
         */
        public void writeSummary(final Appendable appendable) throws IOException {
            appendable.append("RAT summary:").append(System.lineSeparator());
            for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) {
                appendable.append("  ").append(counter.displayName()).append(":  ")
                        .append(Integer.toString(statistic.getCounter(counter)))
                        .append(System.lineSeparator());
            }
        }

        public static final class Builder {
            /**
             * The document that was generated.
             */
            private Document document;
            /**
             * The claim statistic from the execution that generated the document.
             * May be empty if the Document was read from disk.
             */
            private ClaimStatistic statistic;
            /**
             * The configuration that generated the document
             */
            private ReportConfiguration configuration;

            public Builder document(final Document document) {
                this.document = document;
                return this;
            }

            public Builder document(final String fileName, final DocumentName workingDirectory) {
                File inputFile = workingDirectory.resolve(fileName).asFile();
                try (InputStream inputStream = new FileInputStream(inputFile)) {
                    this.document = StandardXmlFactory.documentBuilder().parse(inputStream);
                } catch (SAXException | IOException e) {
                    throw new ConfigurationException("Unable to read file: " + inputFile, e);
                }
                return this;
            }

            public Output build() {
                return new Output(this);
            }

            public Builder statistic(final ClaimStatistic statistic) {
                this.statistic = statistic;
                return this;
            }

            public Builder statistic(final String fileName, final DocumentName workingDirectory) {
                File sourceFile = workingDirectory.resolve(fileName).asFile();
                try {
                    ClaimStatistic newStatistic = new ClaimStatistic();
                    newStatistic.serDes().deserialize(() -> new FileInputStream(sourceFile));
                    this.statistic = newStatistic;
                    return this;
                } catch (IOException e) {
                    throw new ConfigurationException("Unable to read file: " + sourceFile, e);
                }
            }

            public Builder configuration(final ReportConfiguration configuration) {
                this.configuration = configuration;
                return this;
            }

            public Builder configuration(final String fileName, final DocumentName workingDirectory) {
                File configurationFile = workingDirectory.resolve(fileName).asFile();
                try {
                    ReportConfiguration config = new ReportConfiguration();
                    config.serDes().deserialize(() -> new FileInputStream(configurationFile), workingDirectory);
                    this.configuration = config;
                    return this;
                } catch (IOException e) {
                    throw new ConfigurationException("Unable to read file: " + configurationFile, e);
                }
            }
        }
    }
}