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