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.tools;
20  
21  import java.io.CharArrayWriter;
22  import java.io.FileWriter;
23  import java.io.IOException;
24  import java.io.OutputStreamWriter;
25  import java.io.PrintWriter;
26  import java.io.Writer;
27  import java.nio.charset.StandardCharsets;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Deque;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.function.Function;
34  import java.util.function.Predicate;
35  
36  import org.apache.commons.cli.CommandLine;
37  import org.apache.commons.cli.DefaultParser;
38  import org.apache.commons.cli.HelpFormatter;
39  import org.apache.commons.cli.Option;
40  import org.apache.commons.cli.Options;
41  import org.apache.commons.cli.ParseException;
42  import org.apache.commons.csv.CSVFormat;
43  import org.apache.commons.csv.CSVPrinter;
44  import org.apache.commons.csv.QuoteMode;
45  import org.apache.commons.lang3.StringUtils;
46  import org.apache.rat.OptionCollection;
47  import org.apache.rat.help.AbstractHelp;
48  
49  import static java.lang.String.format;
50  
51  /**
52   * A simple tool to convert CLI options to Maven and Ant format and produce a CSV file.
53   * <br>
54   * Options
55   * <ul>
56   *     <li>--ant   Produces Ant options in result</li>
57   *     <li>--maven Produces Maven options in result</li>
58   *     <li>--csv   Produces CSV output text</li>
59   * </ul>
60   * Note: if neither --ant nor --maven are included both will be listed.
61   */
62  public final class Naming {
63  
64      private Naming() { }
65      /** The maximum width of the output. */
66      private static final Option WIDTH = Option.builder().longOpt("width").type(Integer.class)
67              .desc("Set the display width of the output").hasArg().build();
68      /** Option to output Maven names. */
69      private static final Option MAVEN = Option.builder().longOpt("maven").desc("Produce Maven name mapping").build();
70      /** Option to output Ant names. */
71      private static final Option ANT = Option.builder().longOpt("ant").desc("Produce Ant name mapping").build();
72      /** Option to output CSV format. */
73      private static final Option CSV = Option.builder().longOpt("csv").desc("Produce CSV format").build();
74      /** Options to output cli names. */
75      private static final Option CLI = Option.builder().longOpt("cli").desc("Produce CLI name mapping").build();
76      /** Option for including deprecated options. */
77      private static final Option INCLUDE_DEPRECATED = Option.builder().longOpt("include-deprecated")
78              .desc("Include deprecated options.").build();
79      /** The all option. */
80      private static final Options OPTIONS = new Options().addOption(MAVEN).addOption(ANT).addOption(CLI)
81              .addOption(CSV)
82              .addOption(INCLUDE_DEPRECATED)
83              .addOption(WIDTH);
84  
85      /**
86       * Creates the CSV file.
87       * Requires 1 argument:
88       * <ol>
89       *    <li>the name of the output file with path if desired</li>
90       * </ol>
91       * @throws IOException on error
92       * @param args arguments, only 1 is required.
93       */
94      public static void main(final String[] args) throws IOException, ParseException {
95          if (args == null || args.length < 1) {
96              System.err.println("At least one argument is required: path to file is missing.");
97              return;
98          }
99          CommandLine cl = DefaultParser.builder().build().parse(OPTIONS, args);
100         int width = Math.max(cl.getParsedOptionValue(WIDTH, AbstractHelp.HELP_WIDTH), AbstractHelp.HELP_WIDTH);
101 
102         Predicate<Option> mavenFilter = cl.hasOption(MAVEN) ? MavenGenerator.getFilter() : null;
103 
104         Predicate<Option> antFilter = cl.hasOption(ANT) ? AntGenerator.getFilter() : null;
105         boolean includeDeprecated = cl.hasOption(INCLUDE_DEPRECATED);
106         Predicate<Option> filter = o -> o.hasLongOpt() && (!o.isDeprecated() || includeDeprecated);
107 
108         List<String> columns = new ArrayList<>();
109 
110         if (cl.hasOption(CLI)) {
111             columns.add("CLI");
112         }
113 
114         if (antFilter != null) {
115             columns.add("Ant");
116         }
117 
118         if (mavenFilter != null) {
119             columns.add("Maven");
120         }
121         columns.add("Description");
122         columns.add("Argument Type");
123 
124         Function<Option, String> descriptionFunction;
125 
126         if (cl.hasOption(CLI) || antFilter != null && mavenFilter != null) {
127             descriptionFunction = o -> {
128                 StringBuilder desc = new StringBuilder();
129             if (o.isDeprecated()) {
130                 desc.append("[").append(o.getDeprecated().toString()).append("] ");
131             }
132             return desc.append(StringUtils.defaultIfEmpty(o.getDescription(), "")).toString();
133             };
134         } else if (antFilter != null) {
135             descriptionFunction = o -> {
136                 StringBuilder desc = new StringBuilder();
137                 AntOption antOption = new AntOption(o);
138                 if (antOption.isDeprecated()) {
139                     desc.append("[").append(antOption.getDeprecated()).append("] ");
140                 }
141                 return desc.append(StringUtils.defaultIfEmpty(antOption.getDescription(), "")).toString();
142             };
143         } else {
144             descriptionFunction = o -> {
145                 StringBuilder desc = new StringBuilder();
146                 MavenOption mavenOption = new MavenOption(o);
147                 if (mavenOption.isDeprecated()) {
148                     desc.append("[").append(mavenOption.getDeprecated()).append("] ");
149                 }
150                 return desc.append(StringUtils.defaultIfEmpty(mavenOption.getDescription(), "")).toString();
151             };
152         }
153 
154         try (Writer underWriter = cl.getArgs().length != 0 ? new FileWriter(cl.getArgs()[0]) : new OutputStreamWriter(System.out, StandardCharsets.UTF_8)) {
155             if (cl.hasOption(CSV)) {
156                 printCSV(columns, filter, cl.hasOption(CLI), mavenFilter, antFilter, descriptionFunction, underWriter);
157             }
158             else {
159                 printText(columns, filter, cl.hasOption(CLI), mavenFilter, antFilter, descriptionFunction, underWriter, width);
160             }
161         }
162     }
163 
164     private static List<String> fillColumns(final List<String> columns, final Option option, final boolean addCLI, final Predicate<Option> mavenFilter,
165                                             final Predicate<Option> antFilter, final Function<Option, String> descriptionFunction) {
166         if (addCLI) {
167             if (option.hasLongOpt()) {
168                 columns.add("--" + option.getLongOpt());
169             } else {
170                 columns.add("-" + option.getOpt());
171             }
172         }
173         if (antFilter != null) {
174             columns.add(antFilter.test(option) ? antFunctionName(option) : "-- not supported --");
175         }
176         if (mavenFilter != null) {
177             columns.add(mavenFilter.test(option) ? mavenFunctionName(option) : "-- not supported --");
178         }
179 
180         columns.add(descriptionFunction.apply(option));
181         columns.add(option.hasArgName() ? option.getArgName() : option.hasArgs() ? "Strings" : option.hasArg() ? "String" : "-- none --");
182         columns.add(option.hasArgName() ? option.getArgName() : option.hasArgs() ? "Strings" : option.hasArg() ? "String" : "-- none --");
183         columns.add(option.hasArgName() ? option.getArgName() : option.hasArgs() ? "Strings" : option.hasArg() ? "String" : "-- none --");
184         return columns;
185     }
186 
187     private static void printCSV(final List<String> columns, final Predicate<Option> filter, final boolean addCLI, final Predicate<Option> mavenFilter,
188                                  final Predicate<Option> antFilter, final Function<Option, String> descriptionFunction,
189                                  final Writer underWriter) throws IOException {
190         try (CSVPrinter printer = new CSVPrinter(underWriter, CSVFormat.DEFAULT.builder().setQuoteMode(QuoteMode.ALL).build())) {
191             printer.printRecord(columns);
192             for (Option option : OptionCollection.buildOptions().getOptions()) {
193                 if (filter.test(option)) {
194                     columns.clear();
195                     printer.printRecord(fillColumns(columns, option, addCLI, mavenFilter, antFilter, descriptionFunction));
196                 }
197             }
198         }
199     }
200 
201     private static int[] calculateColumnWidth(final int width, final int columnCount, final List<List<String>> page) {
202         int[] columnWidth = new int[columnCount];
203         for (List<String> row : page) {
204             for (int i = 0; i < columnCount; i++) {
205                 columnWidth[i] = Math.max(columnWidth[i], row.get(i).length());
206             }
207         }
208         int extra = 0;
209         int averageWidth = (width - ((columnCount - 1) * 2)) / columnCount;
210         int[] overage = new int[columnCount];
211         int totalOverage = 0;
212         for (int i = 0; i < columnCount; i++) {
213             if (columnWidth[i] < averageWidth) {
214                 extra += averageWidth - columnWidth[i];
215             } else if (columnWidth[i] > averageWidth) {
216                 overage[i] = columnWidth[i] - averageWidth;
217                 totalOverage += overage[i];
218             }
219         }
220 
221         for (int i = 0; i < columnCount; i++) {
222             if (overage[i] > 0) {
223                 int addl = (int) (extra * overage[i] * 1.0 / totalOverage);
224                 columnWidth[i] = averageWidth + addl;
225             }
226         }
227         return columnWidth;
228     }
229 
230     private static void printText(final List<String> columns, final Predicate<Option> filter, final boolean addCLI,
231                                   final Predicate<Option> mavenFilter, final Predicate<Option> antFilter,
232                                   final Function<Option, String> descriptionFunction, final Writer underWriter, final int width) throws IOException {
233         List<List<String>> page = new ArrayList<>();
234 
235         int columnCount = columns.size();
236         page.add(columns);
237 
238         for (Option option : OptionCollection.buildOptions().getOptions()) {
239             if (filter.test(option)) {
240                 page.add(fillColumns(new ArrayList<>(), option, addCLI, mavenFilter, antFilter, descriptionFunction));
241             }
242         }
243         int[] columnWidth = calculateColumnWidth(width, columnCount, page);
244         HelpFormatter helpFormatter;
245         helpFormatter = new HelpFormatter.Builder().get();
246         helpFormatter.setWidth(width);
247 
248 
249         List<Deque<String>> entries = new ArrayList<>();
250         CharArrayWriter cWriter = new CharArrayWriter();
251 
252         // process one line at a time
253         for (List<String> cols : page) {
254             entries.clear();
255             PrintWriter writer = new PrintWriter(cWriter);
256             // print each column into a block of strings.
257             for (int i = 0; i < columnCount; i++) {
258                 String col = cols.get(i);
259                 // split on end of line within a column
260                 for (String line : col.split("\\v")) {
261                     helpFormatter.printWrapped(writer, columnWidth[i], 2, line);
262                 }
263                 writer.flush();
264                 // please the block of strings into a queue.
265                 Deque<String> entryLines = new LinkedList<>(Arrays.asList(cWriter.toString().split("\\v")));
266                 // put the queue into the entries for this line.
267                 entries.add(entryLines);
268                 cWriter.reset();
269             }
270             // print the entries by printing the items from the queues until all queues are empty.
271             boolean cont = true;
272             while (cont) {
273                 cont = false;
274                 for (int columnNumber = 0; columnNumber < entries.size(); columnNumber++) {
275                     Deque<String> queue = entries.get(columnNumber);
276                     if (queue.isEmpty()) {
277                         underWriter.append(AbstractHelp.createPadding(columnWidth[columnNumber] + 2));
278                     } else {
279                         String ln = queue.pop();
280                         underWriter.append(ln);
281                         underWriter.append(AbstractHelp.createPadding(columnWidth[columnNumber] - ln.length() + 2));
282                         if (!queue.isEmpty()) {
283                             cont = true;
284                         }
285                     }
286                 }
287                 underWriter.append(System.lineSeparator());
288             }
289             underWriter.append(System.lineSeparator());
290         }
291     }
292 
293     public static String mavenFunctionName(final Option option) {
294         MavenOption mavenOption = new MavenOption(option);
295         StringBuilder sb = new StringBuilder();
296         if (mavenOption.isDeprecated()) {
297             sb.append("@Deprecated").append(System.lineSeparator());
298         }
299         return sb.append(format("<%s>", mavenOption.getName())).toString();
300     }
301 
302     private static String antFunctionName(final Option option) {
303         StringBuilder sb = new StringBuilder();
304         AntOption antOption = new AntOption(option);
305         if (option.isDeprecated()) {
306             sb.append("@Deprecated").append(System.lineSeparator());
307         }
308         if (option.hasArgs()) {
309             sb.append(format("<rat:report>%n  <%1$s>text</%1$s>%n</rat:report>", antOption.getName()));
310         } else {
311             sb.append(format("<rat:report %s = 'text'/>", antOption.getName()));
312         }
313         return sb.toString();
314     }
315 }