View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   */
19  package org.apache.rat;
20  
21  import org.apache.commons.cli.CommandLine;
22  import org.apache.commons.cli.HelpFormatter;
23  import org.apache.commons.cli.Option;
24  import org.apache.commons.cli.OptionBuilder;
25  import org.apache.commons.cli.OptionGroup;
26  import org.apache.commons.cli.Options;
27  import org.apache.commons.cli.ParseException;
28  import org.apache.commons.cli.PosixParser;
29  import org.apache.commons.io.FileUtils;
30  import org.apache.commons.io.filefilter.NotFileFilter;
31  import org.apache.commons.io.filefilter.OrFileFilter;
32  import org.apache.commons.io.filefilter.RegexFileFilter;
33  import org.apache.commons.io.filefilter.WildcardFileFilter;
34  import org.apache.rat.api.RatException;
35  import org.apache.rat.report.IReportable;
36  import org.apache.rat.report.RatReport;
37  import org.apache.rat.report.claim.ClaimStatistic;
38  import org.apache.rat.report.xml.XmlReportFactory;
39  import org.apache.rat.report.xml.writer.IXmlWriter;
40  import org.apache.rat.report.xml.writer.impl.base.XmlWriter;
41  import org.apache.rat.walker.ArchiveWalker;
42  import org.apache.rat.walker.DirectoryWalker;
43  
44  import javax.xml.transform.TransformerConfigurationException;
45  import java.io.File;
46  import java.io.FileInputStream;
47  import java.io.FileNotFoundException;
48  import java.io.FilenameFilter;
49  import java.io.IOException;
50  import java.io.InputStream;
51  import java.io.OutputStreamWriter;
52  import java.io.PipedReader;
53  import java.io.PipedWriter;
54  import java.io.PrintStream;
55  import java.io.Writer;
56  import java.util.List;
57  
58  
59  public class Report {
60      private static final char EXCLUDE_CLI = 'e';
61      private static final char EXCLUDE_FILE_CLI = 'E';
62      private static final char STYLESHEET_CLI = 's';
63  
64      //@SuppressWarnings("unchecked")
65      public static final void main(String args[]) throws Exception {
66          final ReportConfiguration configuration = new ReportConfiguration();
67          configuration.setHeaderMatcher(Defaults.createDefaultMatcher());
68          configuration.setApproveDefaultLicenses(true);
69          Options opts = buildOptions();
70  
71          PosixParser parser = new PosixParser();
72          CommandLine cl = null;
73          try {
74              cl = parser.parse(opts, args);
75          } catch (ParseException e) {
76              System.err.println("Please use the \"--help\" option to see a list of valid commands and options");
77              System.exit(1);
78              return; // dummy return (won't be reached) to avoid Eclipse complaint about possible NPE for "cl"
79          }
80  
81          if (cl.hasOption('h')) {
82              printUsage(opts);
83          }
84  
85          args = cl.getArgs();
86          if (args == null || args.length != 1) {
87              printUsage(opts);
88          } else {
89              Report report = new Report(args[0]);
90  
91              if (cl.hasOption('a') || cl.hasOption('A')) {
92                  configuration.setAddingLicenses(true);
93                  configuration.setAddingLicensesForced(cl.hasOption('f'));
94                  configuration.setCopyrightMessage(cl.getOptionValue("c"));
95              }
96  
97              if (cl.hasOption(EXCLUDE_CLI)) {
98                  String[] excludes = cl.getOptionValues(EXCLUDE_CLI);
99                  if (excludes != null) {
100                     final FilenameFilter filter = new NotFileFilter(new WildcardFileFilter(excludes));
101                     report.setInputFileFilter(filter);
102                 }
103             } else if (cl.hasOption(EXCLUDE_FILE_CLI)) {
104                 String excludeFileName = cl.getOptionValue(EXCLUDE_FILE_CLI);
105                 if (excludeFileName != null) {
106                     List<String> excludes = FileUtils.readLines(new File(excludeFileName));
107                     final OrFileFilter orFilter = new OrFileFilter();
108                     for (String exclude : excludes) {
109                         orFilter.addFileFilter(new RegexFileFilter(exclude));
110                     }
111                     final FilenameFilter filter = new NotFileFilter(orFilter);
112                     report.setInputFileFilter(filter);
113                 }
114             }
115             if (cl.hasOption('x')) {
116                 report.report(System.out, configuration);
117             } else {
118                 if (!cl.hasOption(STYLESHEET_CLI)) {
119                     report.styleReport(System.out, configuration);
120                 } else {
121                     String[] style = cl.getOptionValues(STYLESHEET_CLI);
122                     if (style.length != 1) {
123                         System.err.println("please specify a single stylesheet");
124                         System.exit(1);
125                     }
126                     try {
127                         report(System.out,
128                                 report.getDirectory(System.out),
129                                 new FileInputStream(style[0]),
130                                 configuration);
131                     } catch (FileNotFoundException fnfe) {
132                         System.err.println("stylesheet " + style[0]
133                                 + " doesn't exist");
134                         System.exit(1);
135                     }
136                 }
137             }
138         }
139     }
140 
141     private static Options buildOptions() {
142         Options opts = new Options();
143 
144         Option help = new Option("h", "help", false,
145                 "Print help for the Rat command line interface and exit");
146         opts.addOption(help);
147 
148         OptionGroup addLicenseGroup = new OptionGroup();
149         String addLicenseDesc = "Add the default license header to any file with an unknown license that is not in the exclusion list. " +
150                 "By default new files will be created with the license header, " +
151                 "to force the modification of existing files use the --force option.";
152 
153         // RAT-85/RAT-203: Deprecated! added only for convenience and for backwards compatibility
154         Option addLicence = new Option(
155                 "a",
156                 "addLicence",
157                 false,
158                 addLicenseDesc);
159         addLicenseGroup.addOption(addLicence);
160         Option addLicense = new Option(
161                 "A",
162                 "addLicense",
163                 false,
164                 addLicenseDesc);
165         addLicenseGroup.addOption(addLicense);
166         opts.addOptionGroup(addLicenseGroup);
167 
168         Option write = new Option(
169                 "f",
170                 "force",
171                 false,
172                 "Forces any changes in files to be written directly to the source files (i.e. new files are not created)");
173         opts.addOption(write);
174 
175         Option copyright = new Option(
176                 "c",
177                 "copyright",
178                 true,
179                 "The copyright message to use in the license headers, usually in the form of \"Copyright 2008 Foo\"");
180         opts.addOption(copyright);
181 
182         @SuppressWarnings("static-access") // ignore OptionBuilder design fault
183         final Option exclude = OptionBuilder
184                 .withArgName("expression")
185                 .withLongOpt("exclude")
186                 .hasArgs()
187                 .withDescription("Excludes files matching wildcard <expression>. " +
188                         "Note that --dir is required when using this parameter. " +
189                         "Allows multiple arguments.")
190                 .create(EXCLUDE_CLI);
191         opts.addOption(exclude);
192 
193         @SuppressWarnings("static-access") // ignore OptionBuilder design fault
194         final Option excludeFile = OptionBuilder
195                 .withArgName("fileName")
196                 .withLongOpt("exclude-file")
197                 .hasArgs()
198                 .withDescription("Excludes files matching regular expression in <file> " +
199                         "Note that --dir is required when using this parameter. ")
200                 .create(EXCLUDE_FILE_CLI);
201         opts.addOption(excludeFile);
202 
203         Option dir = new Option(
204                 "d",
205                 "dir",
206                 false,
207                 "Used to indicate source when using --exclude");
208         opts.addOption(dir);
209 
210         OptionGroup outputType = new OptionGroup();
211 
212         Option xml = new Option(
213                 "x",
214                 "xml",
215                 false,
216                 "Output the report in raw XML format.  Not compatible with -s");
217         outputType.addOption(xml);
218 
219         Option xslt = new Option(String.valueOf(STYLESHEET_CLI),
220                 "stylesheet",
221                 true,
222                 "XSLT stylesheet to use when creating the"
223                         + " report.  Not compatible with -x");
224         outputType.addOption(xslt);
225         opts.addOptionGroup(outputType);
226 
227         return opts;
228     }
229 
230     private static final void printUsage(Options opts) {
231         HelpFormatter f = new HelpFormatter();
232         String header = "Options";
233 
234         StringBuilder footer = new StringBuilder("\nNOTE:\n");
235         footer.append("Rat is really little more than a grep ATM\n");
236         footer.append("Rat is also rather memory hungry ATM\n");
237         footer.append("Rat is very basic ATM\n");
238         footer.append("Rat highlights possible issues\n");
239         footer.append("Rat reports require interpretation\n");
240         footer.append("Rat often requires some tuning before it runs well against a project\n");
241         footer.append("Rat relies on heuristics: it may miss issues\n");
242 
243         f.printHelp("java rat.report [options] [DIR|TARBALL]",
244                 header, opts, footer.toString(), false);
245         System.exit(0);
246     }
247 
248     private final String baseDirectory;
249 
250     private FilenameFilter inputFileFilter = null;
251 
252     private Report(String baseDirectory) {
253         this.baseDirectory = baseDirectory;
254     }
255 
256     /**
257      * Gets the current filter used to select files.
258      *
259      * @return current file filter, or null when no filter has been set
260      */
261     public FilenameFilter getInputFileFilter() {
262         return inputFileFilter;
263     }
264 
265     /**
266      * Sets the current filter used to select files.
267      *
268      * @param inputFileFilter filter, or null when no filter has been set
269      */
270     public void setInputFileFilter(FilenameFilter inputFileFilter) {
271         this.inputFileFilter = inputFileFilter;
272     }
273 
274     /**
275      * @param out - the output stream to receive the styled report
276      * @return the currently collected numerical statistics.
277      * @throws Exception in case of errors.
278      * @deprecated use {@link #report(PrintStream, ReportConfiguration)} instead
279      */
280     @Deprecated
281     public ClaimStatistic report(PrintStream out) throws Exception {
282         final ReportConfiguration configuration = new ReportConfiguration();
283         configuration.setHeaderMatcher(Defaults.createDefaultMatcher());
284         configuration.setApproveDefaultLicenses(true);
285         return report(out, configuration);
286     }
287 
288     /**
289      * @param out           - the output stream to receive the styled report
290      * @param configuration - current configuration options.
291      * @return the currently collected numerical statistics.
292      * @throws Exception in case of errors.
293      * @since Rat 0.8
294      */
295     public ClaimStatistic report(PrintStream out,
296                                  ReportConfiguration configuration)
297             throws Exception {
298         final IReportable base = getDirectory(out);
299         if (base != null) {
300             return report(base, new OutputStreamWriter(out), configuration);
301         }
302         return null;
303     }
304 
305     private IReportable getDirectory(PrintStream out) {
306         File base = new File(baseDirectory);
307         if (!base.exists()) {
308             out.print("ERROR: ");
309             out.print(baseDirectory);
310             out.print(" does not exist.\n");
311             return null;
312         }
313 
314         if (base.isDirectory()) {
315             return new DirectoryWalker(base, inputFileFilter);
316         }
317 
318         try {
319             return new ArchiveWalker(base, inputFileFilter);
320         } catch (IOException ex) {
321             out.print("ERROR: ");
322             out.print(baseDirectory);
323             out.print(" is not valid gzip data.\n");
324             return null;
325         }
326     }
327 
328     /**
329      * Output a report in the default style and default license
330      * header matcher.
331      *
332      * @param out - the output stream to receive the styled report
333      * @throws Exception in case of errors.
334      * @deprecated use {@link #styleReport(PrintStream, ReportConfiguration)} instead
335      */
336     @Deprecated
337     public void styleReport(PrintStream out) throws Exception {
338         final ReportConfiguration configuration = new ReportConfiguration();
339         configuration.setHeaderMatcher(Defaults.createDefaultMatcher());
340         configuration.setApproveDefaultLicenses(true);
341         styleReport(out, configuration);
342     }
343 
344     /**
345      * Output a report in the default style and default license
346      * header matcher.
347      *
348      * @param out           - the output stream to receive the styled report
349      * @param configuration the configuration to use
350      * @throws Exception in case of errors.
351      * @since Rat 0.8
352      */
353     public void styleReport(PrintStream out,
354                             ReportConfiguration configuration)
355             throws Exception {
356         final IReportable base = getDirectory(out);
357         if (base != null) {
358             InputStream style = Defaults.getDefaultStyleSheet();
359             report(out, base, style, configuration);
360         }
361     }
362 
363     /**
364      * Output a report that is styled using a defined stylesheet.
365      *
366      * @param out            the stream to write the report to
367      * @param base           the files or directories to report on
368      * @param style          an input stream representing the stylesheet to use for styling the report
369      * @param pConfiguration current report configuration.
370      * @throws IOException                       in case of I/O errors.
371      * @throws TransformerConfigurationException in case of XML errors.
372      * @throws InterruptedException              in case of threading errors.
373      * @throws RatException                      in case of internal errors.
374      */
375     public static void report(PrintStream out, IReportable base, final InputStream style,
376                               ReportConfiguration pConfiguration)
377             throws IOException, TransformerConfigurationException, InterruptedException, RatException {
378         report(new OutputStreamWriter(out), base, style, pConfiguration);
379     }
380 
381     /**
382      * Output a report that is styled using a defined stylesheet.
383      *
384      * @param out            the writer to write the report to
385      * @param base           the files or directories to report on
386      * @param style          an input stream representing the stylesheet to use for styling the report
387      * @param pConfiguration current report configuration.
388      * @return the currently collected numerical statistics.
389      * @throws IOException                       in case of I/O errors.
390      * @throws TransformerConfigurationException in case of XML errors.
391      * @throws InterruptedException              in case of threading errors.
392      * @throws RatException                      in case of internal errors.
393      */
394     public static ClaimStatistic report(Writer out, IReportable base, final InputStream style,
395                                         ReportConfiguration pConfiguration)
396             throws IOException, TransformerConfigurationException, InterruptedException, RatException {
397         PipedReader reader = new PipedReader();
398         PipedWriter writer = new PipedWriter(reader);
399         ReportTransformer transformer = new ReportTransformer(out, style, reader);
400         Thread transformerThread = new Thread(transformer);
401         transformerThread.start();
402         final ClaimStatistic statistic = report(base, writer, pConfiguration);
403         writer.flush();
404         writer.close();
405         transformerThread.join();
406         return statistic;
407     }
408 
409     /**
410      * @param container      the files or directories to report on
411      * @param out            the writer to write the report to
412      * @param pConfiguration current report configuration.
413      * @return the currently collected numerical statistics.
414      * @throws IOException  in case of I/O errors.
415      * @throws RatException in case of internal errors.
416      */
417     public static ClaimStatistic report(final IReportable container, final Writer out,
418                                         ReportConfiguration pConfiguration) throws IOException, RatException {
419         IXmlWriter writer = new XmlWriter(out);
420         final ClaimStatistic statistic = new ClaimStatistic();
421         RatReport report = XmlReportFactory.createStandardReport(writer, statistic, pConfiguration);
422         report.startReport();
423         container.run(report);
424         report.endReport();
425         writer.closeDocument();
426         return statistic;
427     }
428 }