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.help;
20  
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.io.Writer;
24  import java.lang.reflect.InvocationTargetException;
25  import java.util.Collection;
26  import java.util.Comparator;
27  import java.util.Map;
28  import java.util.SortedSet;
29  import java.util.TreeSet;
30  import java.util.UUID;
31  
32  import org.apache.commons.cli.HelpFormatter;
33  import org.apache.commons.lang3.StringUtils;
34  import org.apache.rat.ConfigurationException;
35  import org.apache.rat.ReportConfiguration;
36  import org.apache.rat.analysis.IHeaderMatcher;
37  import org.apache.rat.config.parameters.ComponentType;
38  import org.apache.rat.config.parameters.Description;
39  import org.apache.rat.config.parameters.DescriptionBuilder;
40  import org.apache.rat.configuration.MatcherBuilderTracker;
41  import org.apache.rat.configuration.builders.AbstractBuilder;
42  import org.apache.rat.license.ILicense;
43  import org.apache.rat.license.ILicenseFamily;
44  import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
45  
46  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
47  
48  import static java.lang.String.format;
49  
50  /**
51   * Generates text based documentation for Licenses, LicenceFamilies, and Matchers.
52   * Utilizes the same command line as the CLI based Report client so that additional licenses, etc. can be added.
53   */
54  public final class Licenses extends AbstractHelp {
55      /** The report configuration to extract licenses from */
56      private final ReportConfiguration config;
57      /** The licenses in the report config */
58      private final SortedSet<ILicense> licenses;
59      /** The formatter */
60      private final HelpFormatter formatter;
61      /** The writer to write to */
62      private final PrintWriter printWriter;
63  
64      /**
65       * Constructor
66       * @param config The configuration that contains the license information.
67       * @param writer the writer to write the report to.
68       */
69      @SuppressFBWarnings("EI_EXPOSE_REP2")
70      public Licenses(final ReportConfiguration config, final Writer writer) {
71          this.config = config;
72          this.licenses = config.getLicenses(LicenseFilter.ALL);
73          printWriter = new PrintWriter(writer);
74          formatter = HelpFormatter.builder().setShowDeprecated(false).setPrintWriter(printWriter).get();
75      }
76  
77      /**
78       * Prints the text indented and wrapped.
79       * @param indent the number of spaces to indent.
80       * @param text the text to write.
81       */
82      void print(final int indent, final String text) {
83          int leftMargin = indent * HELP_PADDING;
84          int tabStop = leftMargin + (HELP_PADDING / 2);
85          formatter.printWrapped(printWriter, HELP_WIDTH, tabStop, createPadding(leftMargin) + text);
86      }
87  
88      /**
89       * Print the help text with the version information.
90       * @throws IOException on output error.
91       */
92      public void printHelp() throws IOException {
93          print(0, format("Listing of licenses for %s", versionInfo));
94          output();
95      }
96  
97      /**
98       * Output the License information without the version information.
99       * @throws IOException on error.
100      */
101     public void output() throws IOException {
102 
103         print(0, header("LICENSES"));
104 
105         if (licenses.isEmpty()) {
106             print(0, "No licenses defined");
107         } else {
108             Description licenseDescription = DescriptionBuilder.build(licenses.first());
109             Collection<Description> licenseParams = licenseDescription.filterChildren(d -> d.getType() == ComponentType.PARAMETER);
110 
111             print(0, format("Licenses have the following properties:%n"));
112 
113             for (Description param : licenseParams) {
114                 print(1, format("%s: %s%n", param.getCommonName(), param.getDescription()));
115             }
116 
117             print(0, format("%nThe defined licenses are:%n"));
118             for (ILicense l : licenses) {
119                 print(0, System.lineSeparator());
120                 printObject(0, l);
121             }
122         }
123         print(0, header("DEFINED MATCHERS"));
124         SortedSet<Description> matchers = new TreeSet<>(Comparator.comparing(Description::getCommonName));
125         for (Class<? extends AbstractBuilder> mClazz : MatcherBuilderTracker.instance().getClasses()) {
126             try {
127                 AbstractBuilder builder = mClazz.getConstructor().newInstance();
128                 matchers.add(builder.getDescription());
129             } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
130                      | IllegalArgumentException | InvocationTargetException e) {
131                 throw new ConfigurationException(
132                         format("Can not instantiate matcher builder  %s ", mClazz.getName()), e);
133             }
134         }
135         for (Description description : matchers) {
136             print(0, format("%n%s: %s%n", description.getCommonName(), description.getDescription()));
137             for (Description child : description.getChildren().values()) {
138                 if (IHeaderMatcher.class.isAssignableFrom(child.getChildType())) {
139                     if (child.isCollection()) {
140                         print(2, "Encloses multiple matchers");
141                     } else {
142                         print(2, "Wraps a single matcher");
143                     }
144                 } else {
145                     print(1, format("%s: %s%s", child.getCommonName(), child.isRequired() ? "(required) " : "", child.getDescription()));
146                 }
147             }
148         }
149 
150         print(0, header("DEFINED FAMILIES"));
151         for (ILicenseFamily family : config.getLicenseFamilies(LicenseFilter.ALL)) {
152             print(1, format("%s - %s%n", family.getFamilyCategory(), family.getFamilyName()));
153         }
154         printWriter.flush();
155     }
156 
157     /**
158      * Print the description of an object.
159      * @param indent the number of spaces to indent the print.
160      * @param object the object to print.
161      * @throws IOException on output error.
162      */
163     private void printObject(final int indent, final Object object) throws IOException {
164         if (object == null) {
165             return;
166         }
167         Description description = DescriptionBuilder.build(object);
168         if (description == null) {
169             print(indent, format("Unknown Object of class: %s%n", object.getClass().getName()));
170         } else {
171             print(indent, format("%s (%s)%n", description.getCommonName(), description.getDescription()));
172             printChildren(indent + 1, object, description.getChildren());
173         }
174     }
175 
176     /**
177      * Returns {@code true} if the string is a UUID.
178      * @param s the string to check.
179      * @return {@code true} if the string is a UUID.
180      */
181     private boolean isUUID(final String s) {
182         try {
183             UUID.fromString(s);
184             return true;
185         } catch (IllegalArgumentException expected) {
186            return false;
187         }
188     }
189 
190     /**
191      * Print the information for the children.
192      * @param indent the number of spaces to indent.
193      * @param parent the parent object.
194      * @param children the children of the parent.
195      * @throws IOException on write error.
196      */
197     private void printChildren(final int indent, final Object parent, final Map<String, Description> children) throws IOException {
198         for (Description d : children.values()) {
199             switch (d.getType()) {
200                 case PARAMETER:
201                     if (d.isCollection()) {
202                         print(indent, format("%s: %n", d.getCommonName()));
203                         try {
204                             Collection<?> result = (Collection<?>) d.getter(parent.getClass()).invoke(parent);
205                             for (Object o : result) {
206                                 printObject(indent + 1, o);
207                             }
208                             return;
209                         } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
210                             throw new RuntimeException(e);
211                         }
212                     }
213                     if (IHeaderMatcher.class.isAssignableFrom(d.getChildType())) {
214                         print(indent, format("%s: %n", d.getCommonName()));
215                         // is a matcher.
216                         try {
217                             Object matcher = d.getter(parent.getClass()).invoke(parent);
218                             printObject(indent + 1, matcher);
219                         } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
220                             throw new RuntimeException(e);
221                         }
222                     } else {
223                         String txt = StringUtils.defaultIfBlank(d.getParamValue(parent), "").replaceAll("\\s{2,}", " ");
224                         if (!txt.isEmpty() && !(d.getCommonName().equals("id") && isUUID(txt))) {
225                                print(indent, format("%s: %s%n", d.getCommonName(), txt.replaceAll("\\s{2,}", " ")));
226                         }
227                     }
228                     break;
229                 case BUILD_PARAMETER:
230                 case LICENSE:
231                     // do nothing
232                     break;
233                 case MATCHER:
234                     printChildren(indent + 1, parent, d.getChildren());
235                     break;
236             }
237         }
238     }
239 }