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.anttasks;
20  
21   import org.apache.commons.io.IOUtils;
22   import org.apache.rat.Defaults;
23   import org.apache.rat.ReportConfiguration;
24   import org.apache.rat.analysis.IHeaderMatcher;
25   import org.apache.rat.analysis.util.HeaderMatcherMultiplexer;
26   import org.apache.rat.api.RatException;
27   import org.apache.rat.license.ILicenseFamily;
28   import org.apache.tools.ant.BuildException;
29   import org.apache.tools.ant.Project;
30   import org.apache.tools.ant.Task;
31   import org.apache.tools.ant.taskdefs.LogOutputStream;
32   import org.apache.tools.ant.types.EnumeratedAttribute;
33   import org.apache.tools.ant.types.Resource;
34  import org.apache.tools.ant.types.ResourceCollection;
35  import org.apache.tools.ant.types.resources.Union;
36  import org.apache.tools.ant.util.FileUtils;
37  
38  import javax.xml.transform.TransformerException;
39  import java.io.File;
40  import java.io.FileOutputStream;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.io.OutputStreamWriter;
44  import java.io.PrintWriter;
45  import java.nio.charset.Charset;
46  import java.util.ArrayList;
47  import java.util.List;
48  
49  /**
50   * A basic Ant task that generates a report on all files specified by
51   * the nested resource collection(s).
52   *
53   * <p>IHeaderMatcher(s) can be specified as nested elements as well.</p>
54   *
55   * <p>The attribute <code>format</code> defines the output format and
56   * can take the values
57   * <ul>
58   *   <li>xml - Rat's native XML output.</li>
59   *   <li>styled - transforms the XML output using the given
60   *   stylesheet.  The stylesheet attribute must be set as well if this
61   *   attribute is used.</li>
62   *   <li>plain - plain text using Rat's built-in stylesheet.  This is
63   *   the default.</li>
64   * </ul>
65   */
66  public class Report extends Task {
67  
68      /**
69       * will hold any nested resource collection
70       */
71      private Union nestedResources;
72      /**
73       * The licenses we want to match on.
74       */
75      private ArrayList<IHeaderMatcher> licenseMatchers = new ArrayList<IHeaderMatcher>();
76  
77      private ArrayList<ILicenseFamily> licenseNames = new ArrayList<ILicenseFamily>();
78  
79      /**
80       * Whether to add the default list of license matchers.
81       */
82      private boolean addDefaultLicenseMatchers = true;
83      /**
84       * Where to send the report.
85       */
86      private File reportFile;
87      /**
88       * Which format to use.
89       */
90      private Format format = Format.PLAIN;
91      /**
92       * Which stylesheet to use.
93       */
94      private Resource stylesheet;
95      /**
96       * Whether to add license headers.
97       */
98      private AddLicenseHeaders addLicenseHeaders = new AddLicenseHeaders(AddLicenseHeaders.FALSE);
99      /**
100      * The copyright message.
101      */
102     private String copyrightMessage;
103 
104     /**
105      * Adds resources that will be checked.
106      * @param rc resource to check.
107      */
108     public void add(ResourceCollection rc) {
109         if (nestedResources == null) {
110             nestedResources = new Union();
111         }
112         nestedResources.add(rc);
113     }
114 
115     /**
116      * @param matcher Adds a license matcher.
117      */
118     public void add(IHeaderMatcher matcher) {
119         licenseMatchers.add(matcher);
120     }
121 
122     public void add(ILicenseFamily license) {
123         licenseNames.add(license);
124     }
125 
126     /**
127      * @param addDefaultLicenseMatchers Whether to add the default list of license matchers.
128      */
129     public void setAddDefaultLicenseMatchers(boolean addDefaultLicenseMatchers) {
130         this.addDefaultLicenseMatchers = addDefaultLicenseMatchers;
131     }
132 
133     /**
134      * Where to send the report to.
135      * @param f report output file.
136      */
137     public void setReportFile(File f) {
138         reportFile = f;
139     }
140 
141     /**
142      * Which format to use.
143      * @param f format. 
144      */
145     public void setFormat(Format f) {
146         if (f == null) {
147             throw new IllegalArgumentException("format must not be null");
148         }
149         format = f;
150     }
151 
152     /**
153      * @param pAdd Whether to add license headers. 
154      */
155     public void setAddLicenseHeaders(AddLicenseHeaders pAdd) {
156         if (pAdd == null) {
157             throw new IllegalArgumentException("addLicenseHeaders must not be null");
158         }
159         addLicenseHeaders = pAdd;
160     }
161 
162     /**
163      * @param pMessage copyright message to set.
164      */
165     public void setCopyrightMessage(String pMessage) {
166         copyrightMessage = pMessage;
167     }
168     
169     /**
170      * Which stylesheet to use (only meaningful with format='styled').
171      * @param u stylesheet.
172      */
173     public void addConfiguredStylesheet(Union u) {
174         if (stylesheet != null || u.size() != 1) {
175             throw new BuildException("You must not specify more than one stylesheet.");
176         }
177         stylesheet = u.iterator().next();
178     }
179 
180     /**
181      * Generates the report.
182      */
183     @Override
184     public void execute() {
185         validate();
186 
187         PrintWriter out = null;
188         try {
189             if (reportFile == null) {
190                 out = new PrintWriter(
191                           new OutputStreamWriter(
192                               new LogOutputStream(this, Project.MSG_INFO)
193                           ));
194             } else {
195                 out = new PrintWriter(new OutputStreamWriter(
196                         new FileOutputStream(reportFile),
197                         Charset.forName("UTF-8")
198                 ));
199             }
200             createReport(out);
201             out.flush();
202         } catch (IOException ioex) {
203             throw new BuildException(ioex);
204         } catch (TransformerException e) {
205             throw new BuildException(e);
206         } catch (InterruptedException e) {
207             throw new BuildException(e);
208         } catch (RatException e) {
209             throw new BuildException(e);
210         } finally {
211             IOUtils.closeQuietly(out);
212         }
213     }
214 
215     /**
216      * validates the task's configuration.
217      */
218     private void validate() {
219         if (nestedResources == null) {
220             throw new BuildException("You must specify at least one file to"
221                                      + " create the report for.");
222         }
223         if (!addDefaultLicenseMatchers && licenseMatchers.size() == 0) {
224             throw new BuildException("You must specify at least one license"
225                                      + " matcher");
226         }
227         if (format.getValue().equals(Format.STYLED_KEY)) {
228             if (stylesheet == null) {
229                 throw new BuildException("You must specify a stylesheet when"
230                                          + " using the 'styled' format");
231             }
232             if (!stylesheet.isExists()) {
233                 throw new BuildException("Cannot find specified stylesheet '"
234                                          + stylesheet + "'");
235             }
236         } else if (stylesheet != null) {
237             log("Ignoring stylesheet '" + stylesheet + "' when using format '"
238                 + format.getValue() + "'", Project.MSG_WARN);
239         }
240     }
241 
242     /**
243      * Writes the report to the given stream.
244      * 
245      * @param out stream to write report to.
246      * 
247      * @throws IOException in case of I/O errors.
248      * @throws InterruptedException in case of threading errors.
249      * @throws TransformerException in case of XML errors.
250      * @throws RatException in case of general errors.
251      */
252     private void createReport(PrintWriter out) throws IOException, TransformerException, InterruptedException, RatException {
253         final ReportConfiguration configuration = new ReportConfiguration();
254         configuration.setHeaderMatcher(new HeaderMatcherMultiplexer(getLicenseMatchers()));
255         configuration.setApprovedLicenseNames(getApprovedLicenseNames());
256         configuration.setApproveDefaultLicenses(addDefaultLicenseMatchers);
257         
258         if (AddLicenseHeaders.FORCED.equalsIgnoreCase(addLicenseHeaders.getValue())) {
259             configuration.setAddingLicenses(true);
260             configuration.setAddingLicensesForced(true);
261             configuration.setCopyrightMessage(copyrightMessage);
262         } else if (AddLicenseHeaders.TRUE.equalsIgnoreCase(addLicenseHeaders.getValue())) {
263             configuration.setAddingLicenses(true);
264             configuration.setCopyrightMessage(copyrightMessage);
265         } else if (!AddLicenseHeaders.FALSE.equalsIgnoreCase(addLicenseHeaders.getValue())) {
266             throw new BuildException("Invalid value for addLicenseHeaders: " + addLicenseHeaders.getValue());
267         }
268         ResourceCollectionContainer rcElement = new ResourceCollectionContainer(nestedResources);
269         if (format.getValue().equals(Format.XML_KEY)) {
270             org.apache.rat.Report.report(rcElement, out, configuration);
271         } else {
272             InputStream style = null;
273             try {
274                 if (format.getValue().equals(Format.PLAIN_KEY)) {
275                     style = Defaults.getPlainStyleSheet();
276                 } else if (format.getValue().equals(Format.STYLED_KEY)) {
277                     style = stylesheet.getInputStream();
278                 } else {
279                     throw new BuildException("unsupported format '"
280                                              + format.getValue() + "'");
281                 }
282                 org.apache.rat.Report.report(out, rcElement, style,
283                                              configuration);
284             } finally {
285                 FileUtils.close(style);
286             }
287         }
288     }
289 
290     /**
291      * Flattens all nested matchers plus the default matchers (if
292      * required) into a single array.
293      */
294     private List<IHeaderMatcher> getLicenseMatchers() {
295         List<IHeaderMatcher> matchers = new ArrayList<IHeaderMatcher>(
296         (addDefaultLicenseMatchers ? Defaults.DEFAULT_MATCHERS.size() : 0) + licenseMatchers.size());
297         if (addDefaultLicenseMatchers) {
298             matchers.addAll(Defaults.DEFAULT_MATCHERS);
299             matchers.addAll(licenseMatchers);
300         } else {
301             matchers = new ArrayList<IHeaderMatcher>(licenseMatchers);
302         }
303         return matchers;
304     }
305 
306     private ILicenseFamily[] getApprovedLicenseNames() {
307         // TODO: add support for adding default licenses
308         ILicenseFamily[] results = null;
309         if (licenseNames.size() > 0) {
310             results = licenseNames.toArray(new ILicenseFamily[0]);
311         }
312         return results;
313     }
314 
315     /**
316      * Type for the format attribute.
317      */
318     public static class Format extends EnumeratedAttribute {
319         static final String XML_KEY = "xml";
320         static final String STYLED_KEY = "styled";
321         static final String PLAIN_KEY = "plain";
322 
323         static final Format PLAIN = new Format(PLAIN_KEY);
324 
325         public Format() { super(); }
326 
327         private Format(String s) {
328             this();
329             setValue(s);
330         }
331 
332         @Override
333         public String[] getValues() {
334             return new String[] {
335                 XML_KEY, STYLED_KEY, PLAIN_KEY
336             };
337         }
338     }
339 
340     /**
341      * Type for the addLicenseHeaders attribute.
342      */
343     public static class AddLicenseHeaders extends EnumeratedAttribute {
344         static final String TRUE = "true";
345         static final String FALSE = "false";
346         static final String FORCED = "forced";
347 
348         public AddLicenseHeaders() {}
349         public AddLicenseHeaders(String s) {
350             setValue(s);
351         }
352         
353         @Override
354         public String[] getValues() {
355             return new String[] {
356                 TRUE, FALSE, FORCED
357             };
358         }
359     }
360 }