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