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